@neeloong/form 0.26.0 → 0.28.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/index.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * @neeloong/form v0.26.0
2
+ * @neeloong/form v0.28.0
3
3
  * (c) 2024-2026 Fierflame
4
4
  * @license Apache-2.0
5
5
  */
@@ -138,7 +138,7 @@ function createRef(store) {
138
138
 
139
139
 
140
140
  /** @type {{new(...p: ConstructorParameters<typeof Store>): Store}?} */
141
- let ObjectStore$1 = null;
141
+ let ObjectStore$2 = null;
142
142
  /** @type {{new(...p: ConstructorParameters<typeof Store>): ArrayStore}?} */
143
143
  let ArrayStoreClass = null;
144
144
  /** @type {Record<string, {new(...p: ConstructorParameters<typeof Store>): Store}?>} */
@@ -163,14 +163,14 @@ function create(schema, options) {
163
163
  const C = TypeStores[type];
164
164
  if (C) { Class = C; }
165
165
  } else if (type && typeof type === 'object') {
166
- if (ObjectStore$1) { Class = ObjectStore$1; }
166
+ if (ObjectStore$2) { Class = ObjectStore$2; }
167
167
  }
168
168
  return new Class(schema, options);
169
169
  }
170
170
 
171
171
  /** @param {{new(...p: ConstructorParameters<typeof Store>): Store}} Class */
172
172
  function setObjectStore(Class) {
173
- ObjectStore$1 = Class;
173
+ ObjectStore$2 = Class;
174
174
  }
175
175
 
176
176
  /** @param {{new(...p: ConstructorParameters<typeof Store>): ArrayStore}} Class */
@@ -292,6 +292,52 @@ function makeDefault(store, def) {
292
292
  return (value) => structuredClone(def(store, value));
293
293
  }
294
294
 
295
+ /** @import { Schema } from '../Schema.types.mjs' */
296
+
297
+ /**
298
+ * @template [T=any]
299
+ * @template [M=any]
300
+ * @template {Object.<string, Schema.State>} [S=Object.<string, Schema.State>]
301
+ * @extends {Store<T, M, S>}
302
+ */
303
+ class BindObjectStore extends Store {
304
+
305
+ get kind() { return 'object'; }
306
+ /** @type {Record<string, Store>} */
307
+ #children = Object.create(null);
308
+ *[Symbol.iterator]() { yield* Object.entries(this.#children); }
309
+ /**
310
+ *
311
+ * @param {string | number} key
312
+ * @returns {Store?}
313
+ */
314
+ child(key) { return this.#children[key] || null; }
315
+ /**
316
+ * @param {Schema<any, Object.<string, Schema.State>>} schema 数据结构模式
317
+ * @param {Store<T, M, S>} store
318
+ */
319
+ constructor(schema, store) {
320
+ super(store);
321
+ const children = this.#children;
322
+ for (const [index, field] of Object.entries(schema)) {
323
+ const bindStore = create(field, {
324
+ index, parent: this,
325
+ /** @param {*} value @param {*} index @param {Store} store */
326
+ onUpdate: (value, index, store) => {
327
+ if (store !== children[index]) { return; }
328
+ const val = this.value ?? null;
329
+ if (typeof val !== 'object' || Array.isArray(val)) { return; }
330
+ // @ts-ignore
331
+ this.value = { ...val, [index]: value };
332
+ },
333
+ });
334
+ children[index] = bindStore;
335
+ }
336
+ }
337
+ }
338
+ // @ts-ignore
339
+ setObjectStore(ObjectStore);
340
+
295
341
  /** @import { Ref } from './ref.mjs' */
296
342
  /** @import { Schema } from '../Schema.types.mjs' */
297
343
  /** @import { StoreLayout } from '../StoreLayout.types.mjs' */
@@ -303,6 +349,12 @@ function makeDefault(store, def) {
303
349
  * @template {Object.<string, Schema.State>} [S=Object.<string, Schema.State>]
304
350
  */
305
351
  class Store {
352
+ /** @type {Store<T, M, S>?} */
353
+ #originStore = null;
354
+
355
+ /** @type {Schema.Field<M, S>} 字段的 Schema 定义 */
356
+ #schema;
357
+ get schema() { return this.#schema; }
306
358
  /** @type {Map<string, Set<(value: any, store: any) => void | boolean | null>>} */
307
359
  #events = new Map();
308
360
  /**
@@ -310,8 +362,11 @@ class Store {
310
362
  * @template {keyof Schema.Events} K
311
363
  * @param {K} event
312
364
  * @param {Schema.Events[K]} value
365
+ * @returns {boolean}
313
366
  */
314
367
  emit(event, value) {
368
+ const originStore = this.#originStore;
369
+ if (originStore) { return originStore.emit(event, value); }
315
370
  const key = typeof event === 'number' ? String(event) : event;
316
371
  const events = this.#events;
317
372
  let canceled = false;
@@ -328,6 +383,8 @@ class Store {
328
383
  * @returns {() => void}
329
384
  */
330
385
  listen(event, listener) {
386
+ const originStore = this.#originStore;
387
+ if (originStore) { return originStore.listen(event, p => listener.call(this, p, this)); }
331
388
  const fn = listener.bind(this);
332
389
  const events = this.#events;
333
390
  const key = typeof event === 'number' ? String(event) : event;
@@ -368,7 +425,7 @@ class Store {
368
425
  #ref = null;
369
426
  get ref() { return this.#ref || createRef(this); }
370
427
  /**
371
- * @param {Schema.Field<M, S>} schema 字段的 Schema 定义
428
+ * @param {Schema.Field<M, S> | Store<T,M,S>} schema 字段的 Schema 定义
372
429
  * @param {object} [options] 可选配置
373
430
  * @param {Store?} [options.parent]
374
431
  * @param {Partial<S>?} [options.states]
@@ -412,7 +469,74 @@ class Store {
412
469
  hidden, clearable, required, disabled, readonly, removable,
413
470
  label, description, placeholder, min, max, step, minLength, maxLength, pattern, values: values$1
414
471
  } = {}) {
415
- this.schema = schema;
472
+ if (schema instanceof Store) {
473
+ this.#originStore = schema;
474
+ this.#schema = schema.#schema;
475
+ this.#null = schema.#null;
476
+ this.#ref = schema.#ref;
477
+ this.#states = schema.#states;
478
+ this.#layout = schema.#layout;
479
+ this.#createDefault = schema.#createDefault;
480
+ this.#setValue = schema.#setValue;
481
+ this.#convert = schema.#convert;
482
+ this.#onUpdate = schema.#onUpdate;
483
+ this.#parent = schema.#parent;
484
+ this.#root = schema.#root;
485
+ this.#type = schema.#type;
486
+ this.#meta = schema.#meta;
487
+ this.#component = schema.#component;
488
+ this.#selfLoading = schema.#selfLoading;
489
+ this.#loading = schema.#loading;
490
+ this.#size = schema.#size;
491
+ this.#index = schema.#index;
492
+ this.#creatable = schema.#creatable;
493
+ this.#immutable = schema.#immutable;
494
+ this.#new = schema.#new;
495
+ this.#selfNew = schema.#selfNew;
496
+ this.#selfHidden = schema.#selfHidden;
497
+ this.#hidden = schema.#hidden;
498
+ this.#selfClearable = schema.#selfClearable;
499
+ this.#clearable = schema.#clearable;
500
+ this.#selfRequired = schema.#selfRequired;
501
+ this.#required = schema.#required;
502
+ this.#selfDisabled = schema.#selfDisabled;
503
+ this.#disabled = schema.#disabled;
504
+ this.#selfReadonly = schema.#selfReadonly;
505
+ this.#readonly = schema.#readonly;
506
+ this.#selfRemovable = schema.#selfRemovable;
507
+ this.#removable = schema.#removable;
508
+ this.#selfLabel = schema.#selfLabel;
509
+ this.#label = schema.#label;
510
+ this.#selfDescription = schema.#selfDescription;
511
+ this.#description = schema.#description;
512
+ this.#selfPlaceholder = schema.#selfPlaceholder;
513
+ this.#placeholder = schema.#placeholder;
514
+ this.#selfMin = schema.#selfMin;
515
+ this.#min = schema.#min;
516
+ this.#selfMax = schema.#selfMax;
517
+ this.#max = schema.#max;
518
+ this.#selfStep = schema.#selfStep;
519
+ this.#step = schema.#step;
520
+ this.#selfMinLength = schema.#selfMinLength;
521
+ this.#minLength = schema.#minLength;
522
+ this.#selfMaxLength = schema.#selfMaxLength;
523
+ this.#maxLength = schema.#maxLength;
524
+ this.#selfPattern = schema.#selfPattern;
525
+ this.#pattern = schema.#pattern;
526
+ this.#selfValues = schema.#selfValues;
527
+ this.#values = schema.#values;
528
+ this.#errors = schema.#errors;
529
+ this.#validatorResult = schema.#validatorResult;
530
+ this.#changed = schema.#changed;
531
+ this.#blurred = schema.#blurred;
532
+ this.#cancelChange = schema.#cancelChange;
533
+ this.#cancelBlur = schema.#cancelBlur;
534
+ this.#set = schema.#set;
535
+ this.#initValue = schema.#initValue;
536
+ this.#value = schema.#value;
537
+ return;
538
+ }
539
+ this.#schema = schema;
416
540
  const parent = parentNode instanceof Store ? parentNode : null;
417
541
  if (parent) {
418
542
  this.#parent = parent;
@@ -540,6 +664,7 @@ class Store {
540
664
  /** @type {StoreLayout.Field<any>} */
541
665
  #layout;
542
666
  get layout() { return this.#layout; }
667
+ /** @type {(value?: any) => unknown} */
543
668
  #createDefault;
544
669
  /** @param {any} [value] @returns {any} */
545
670
  createDefault(value) { return this.#createDefault(value); }
@@ -821,40 +946,26 @@ class Store {
821
946
  #initValue = new Signal.State(/** @type {T?} */(null));
822
947
  #value = new Signal.State(this.#initValue.get());
823
948
 
949
+ /** @type {Set<Store>} */
950
+ #subBindStores = new Set();
824
951
  /**
825
952
  * @template [M=any]
826
953
  * @template {Object.<string, Schema.State>} [S=Object.<string, Schema.State>]
827
954
  * @param {Schema<M, S>} schema 数据结构模式
955
+ * @param {AbortSignal} [signal]
956
+ * @returns {BindObjectStore}
828
957
  */
829
- bindObject(schema) {
830
- const bindStores = this.#bindStores;
831
- /** @type {Store[]} */
832
- const list = [];
833
- for (const [index, field] of Object.entries(schema)) {
834
- const bindStore = create(field, {
835
- index, parent: this,
836
- /** @param {*} value @param {*} currentIndex @param {Store} store */
837
- onUpdate: (value, currentIndex, store) => {
838
- if (index !== currentIndex) { return; }
839
- if (bindStores.has(store)) { return; }
840
- const val = this.#value ?? null;
841
- if (typeof val !== 'object' || Array.isArray(val)) { return }
842
- // @ts-ignore
843
- this.value = { ...val, [currentIndex]: value };
844
- },
845
- });
846
- list.push(bindStore);
847
- bindStores.set(bindStore, index);
848
- }
958
+ bindObject(schema, signal) {
959
+ const originStore = this.#originStore;
960
+ if (originStore) { return originStore.bindObject(schema, signal); }
961
+ const store = new BindObjectStore(schema, this);
962
+ if (signal?.aborted) { return store; }
963
+ const subBindStores = this.#subBindStores;
964
+ subBindStores.add(store);
965
+ signal?.addEventListener('abort', () => subBindStores.delete(store));
849
966
  this.#requestUpdate();
850
- return () => {
851
- for (const bindStore of list) {
852
- bindStores.delete(bindStore);
853
- }
854
- };
967
+ return store;
855
968
  }
856
- /** @type {Map<Store, string>} */
857
- #bindStores = new Map();
858
969
 
859
970
  /** 内容是否已改变 */
860
971
  get changed() { return !Object.is(this.#value.get(), this.#initValue.get()); }
@@ -862,6 +973,8 @@ class Store {
862
973
  /** 字段当前值 */
863
974
  get value() { return this.#value.get(); }
864
975
  set value(v) {
976
+ const originStore = this.#originStore;
977
+ if (originStore) { originStore.value = v; return; }
865
978
  const newValue = this.#setValue?.(v);
866
979
  const val = newValue === undefined ? v : newValue;
867
980
  this.#value.set(val);
@@ -882,6 +995,8 @@ class Store {
882
995
  }
883
996
  /** 重置数据 */
884
997
  reset(value = this.#set ? this.#initValue.get() : this.#createDefault(), isNew = this.#selfNew.get()) {
998
+ const originStore = this.#originStore;
999
+ if (originStore) { originStore.reset(value, isNew); return; }
885
1000
  this.#reset(value, Boolean(isNew));
886
1001
  }
887
1002
  /**
@@ -898,11 +1013,10 @@ class Store {
898
1013
  this.#cancelBlur();
899
1014
  this.#set = true;
900
1015
  if (!value || typeof value !== 'object') {
901
- for (const [, field] of this) {
902
- field.#reset(null, false);
903
- }
904
- for (const [field] of this.#bindStores) {
905
- field.#reset(null, false);
1016
+ for (const bind of [this, ...this.#subBindStores]) {
1017
+ for (const [, field] of bind) {
1018
+ field.#reset(null, false);
1019
+ }
906
1020
  }
907
1021
  this.#value.set(value);
908
1022
  this.#initValue.set(value);
@@ -911,11 +1025,10 @@ class Store {
911
1025
  }
912
1026
  /** @type {*} */
913
1027
  const newValues = Array.isArray(value) ? [...value] : { ...value };
914
- for (const [key, field] of this) {
915
- newValues[key] = field.#reset(Object.hasOwn(newValues, key) ? newValues[key] : undefined, false);
916
- }
917
- for (const [field, key] of this.#bindStores) {
918
- newValues[key] = field.#reset(Object.hasOwn(newValues, key) ? newValues[key] : undefined, false);
1028
+ for (const bind of [this, ...this.#subBindStores]) {
1029
+ for (const [key, field] of bind) {
1030
+ newValues[key] = field.#reset(Object.hasOwn(newValues, key) ? newValues[key] : undefined, false);
1031
+ }
919
1032
  }
920
1033
  this.#value.set(newValues);
921
1034
  this.#initValue.set(newValues);
@@ -949,23 +1062,16 @@ class Store {
949
1062
  // @ts-ignore
950
1063
  let newValues = Array.isArray(val) ? [...val] : { ...val };
951
1064
  let updated = false;
952
- for (const [key, field] of this) {
953
- // @ts-ignore
954
- const data = Object.hasOwn(val, key) ? val[key] : undefined;
955
- const newData = field.#toUpdate(data);
956
- if (Object.is(data, newData)) { continue; }
957
- // @ts-ignore
958
- newValues[key] = newData;
959
- updated = true;
960
- }
961
- for (const [field, key] of this.#bindStores) {
962
- // @ts-ignore
963
- const data = Object.hasOwn(val, key) ? val[key] : undefined;
964
- const newData = field.#toUpdate(data);
965
- if (Object.is(data, newData)) { continue; }
966
- // @ts-ignore
967
- newValues[key] = newData;
968
- updated = true;
1065
+ for (const bind of [this,...this.#subBindStores]) {
1066
+ for (const [key, field] of bind) {
1067
+ // @ts-ignore
1068
+ const data = Object.hasOwn(val, key) ? val[key] : undefined;
1069
+ const newData = field.#toUpdate(data);
1070
+ if (Object.is(data, newData)) { continue; }
1071
+ // @ts-ignore
1072
+ newValues[key] = newData;
1073
+ updated = true;
1074
+ }
969
1075
  }
970
1076
  if (updated) {
971
1077
  val = newValues;
@@ -999,6 +1105,7 @@ class Store {
999
1105
  */
1000
1106
  validate(path) {
1001
1107
  if (path === true) {
1108
+ if (this.#originStore) { return Promise.resolve(null); }
1002
1109
  return Promise.all([this.#validatorResult.get(), this.#changed(), this.#blurred()])
1003
1110
  .then(v => {
1004
1111
  const errors = v.flat();
@@ -1006,7 +1113,7 @@ class Store {
1006
1113
  });
1007
1114
  }
1008
1115
  const selfPath = Array.isArray(path) ? path : [];
1009
- if (this.#hidden.get()) { return Promise.resolve([]); }
1116
+ if (!this.#originStore && this.#hidden.get()) { return Promise.resolve([]); }
1010
1117
  const list = [this.validate(true).then(errors => {
1011
1118
  if (!errors?.length) { return []; }
1012
1119
  return [{ path: [...selfPath], store: /** @type {Store} */(this), errors }];
@@ -1014,8 +1121,8 @@ class Store {
1014
1121
  for (const [key, field] of this) {
1015
1122
  list.push(field.validate([...selfPath, key]));
1016
1123
  }
1017
- for (const [field, key] of this.#bindStores) {
1018
- list.push(field.validate([...selfPath, key]));
1124
+ for (const sub of this.#subBindStores) {
1125
+ list.push(sub.validate(selfPath));
1019
1126
  }
1020
1127
  return Promise.all(list).then(v => v.flat());
1021
1128
  }
@@ -1029,7 +1136,7 @@ class Store {
1029
1136
  * @template {Object.<string, Schema.State>} [S=Object.<string, Schema.State>]
1030
1137
  * @extends {Store<T, M, S>}
1031
1138
  */
1032
- class ObjectStore extends Store {
1139
+ let ObjectStore$1 = class ObjectStore extends Store {
1033
1140
  get kind() { return 'object'; }
1034
1141
  /** @type {Record<string, Store>} */
1035
1142
  #children;
@@ -1082,9 +1189,9 @@ class ObjectStore extends Store {
1082
1189
  }
1083
1190
  this.#children = children;
1084
1191
  }
1085
- }
1192
+ };
1086
1193
  // @ts-ignore
1087
- setObjectStore(ObjectStore);
1194
+ setObjectStore(ObjectStore$1);
1088
1195
 
1089
1196
  /** @import { Schema } from '../Schema.types.mjs' */
1090
1197
 
@@ -4754,7 +4861,7 @@ function renderChild(layout, parent, next, parentEnv, parentTemplates, component
4754
4861
  if (list instanceof ArrayStore) {
4755
4862
  return renderArray(parent, next, list, env, r, layout.sort);
4756
4863
  }
4757
- if (list instanceof ObjectStore) {
4864
+ if (list instanceof ObjectStore$1) {
4758
4865
  return renderObject(parent, next, list, env, r, layout.sort);
4759
4866
  }
4760
4867
  if (typeof list === 'function') {
@@ -4863,6 +4970,8 @@ function renderHtml(store, fieldRenderer, node, options, layout, anchor, dragent
4863
4970
  if (tagName === 'nl-form-field') {
4864
4971
  const field = node.getAttribute('name') || '';
4865
4972
  const mode = node.getAttribute('mode') || '';
4973
+ const renderer = node.getAttribute('renderer') || '';
4974
+ const editable = options?.editable && !node.hasAttribute('non-editable');
4866
4975
  const fieldStore = field ? store.child(field) : store;
4867
4976
  if (!fieldStore) { return; }
4868
4977
  /** @type {HTMLElement?} */
@@ -4872,11 +4981,11 @@ function renderHtml(store, fieldRenderer, node, options, layout, anchor, dragent
4872
4981
  const fieldLayout = field
4873
4982
  ? layout?.fields?.find(createFieldFilter(field)) || fieldStore.layout
4874
4983
  : { ...layout, html: '' };
4875
- el = Form(fieldStore, fieldRenderer, fieldLayout, options);
4984
+ el = Form(fieldStore, fieldRenderer, fieldLayout, {...options, editable});
4876
4985
  break;
4877
4986
  }
4878
4987
  default: {
4879
- el = fieldRenderer(fieldStore, layout.renderer, options);
4988
+ el = fieldRenderer(fieldStore, renderer || layout.renderer, {...options, editable});
4880
4989
  break;
4881
4990
  }
4882
4991
  }
@@ -5078,9 +5187,10 @@ function renderHtml(store, fieldRenderer, node, options, layout, anchor, dragent
5078
5187
  /**
5079
5188
  *
5080
5189
  * @param {string | ParentNode | null} [html]
5190
+ * @param {function(string): string} [sanitize]
5081
5191
  * @returns {ParentNode}
5082
5192
  */
5083
- function getHtmlContent(html) {
5193
+ function getHtmlContent(html, sanitize) {
5084
5194
  if (!html) {
5085
5195
  return document.createElement('template').content;
5086
5196
  }
@@ -5088,7 +5198,7 @@ function getHtmlContent(html) {
5088
5198
  return /** @type {ParentNode} */(html.cloneNode(true));
5089
5199
  }
5090
5200
  const template = document.createElement('template');
5091
- template.innerHTML = html;
5201
+ template.innerHTML = sanitize ? sanitize(html) : html;
5092
5202
  return template.content;
5093
5203
  }
5094
5204
 
@@ -5105,7 +5215,7 @@ function FormFieldInline(store, fieldRenderer, layout, options) {
5105
5215
  if (options?.signal?.aborted) { return null; }
5106
5216
  const html = layout.html;
5107
5217
  if (html) {
5108
- const content = getHtmlContent(html);
5218
+ const content = getHtmlContent(html, options?.sanitizeHtml);
5109
5219
  renderHtml(store, fieldRenderer, content, options, layout);
5110
5220
  return content;
5111
5221
  }
@@ -6282,7 +6392,7 @@ function Tree(store, fieldRenderer, layout, options) {
6282
6392
  * @returns {ParentNode}
6283
6393
  */
6284
6394
  function Html(html, store, fieldRenderer, options, layout) {
6285
- const htmlContent = getHtmlContent(html);
6395
+ const htmlContent = getHtmlContent(html, options?.sanitizeHtml);
6286
6396
  renderHtml(store, fieldRenderer, htmlContent, options, layout);
6287
6397
  return htmlContent;
6288
6398
  }
@@ -6389,7 +6499,7 @@ function FormHtml(store, fieldRenderer, layout, options) {
6389
6499
  const html = layout.html;
6390
6500
  if (!html) { return null; }
6391
6501
  const [root, content] = createCell(options?.signal, layout, store);
6392
- const htmlContent = getHtmlContent(html);
6502
+ const htmlContent = getHtmlContent(html, options?.sanitizeHtml);
6393
6503
  renderHtml(store, fieldRenderer, htmlContent, options, layout);
6394
6504
  content.appendChild(htmlContent);
6395
6505
  return root;
@@ -6475,7 +6585,7 @@ function renderStore(store, fieldRenderer, root, layout, options) {
6475
6585
  Form(store, fieldRenderer, storeLayout, options || null, root);
6476
6586
  return;
6477
6587
  }
6478
- const content = getHtmlContent(html);
6588
+ const content = getHtmlContent(html, options?.sanitizeHtml);
6479
6589
  renderHtml(store, fieldRenderer, content, options || null, storeLayout);
6480
6590
  root.appendChild(content);
6481
6591
  options?.signal?.addEventListener('abort', () => {
@@ -6483,4 +6593,4 @@ function renderStore(store, fieldRenderer, root, layout, options) {
6483
6593
  }, { once: true });
6484
6594
  }
6485
6595
 
6486
- export { ArrayStore, index as Layout, ObjectStore, Store, effect, render, renderStore, watch };
6596
+ export { ArrayStore, index as Layout, ObjectStore$1 as ObjectStore, Store, effect, render, renderStore, watch };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@neeloong/form",
3
- "version": "0.26.0",
3
+ "version": "0.28.0",
4
4
  "description": "一个基于响应式数据绑定的表单渲染库",
5
5
  "keywords": [
6
6
  "from",