@neeloong/form 0.4.0 → 0.4.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/README.md CHANGED
@@ -197,6 +197,10 @@ render(store, layouts, app);
197
197
  - `$max` 只读属性
198
198
  - `$step` 只读属性
199
199
  - `$values` 只读属性
200
+ - `$type` 只读属性
201
+ - `$meta` 只读属性
202
+ - `$component` 只读属性
203
+ - `$kind` 只读属性
200
204
  1. 数组字段扩展隐式函数(只在事件中可用)
201
205
  - `$insert(index, value)`
202
206
  - `$add(value)`
package/index.d.mts CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * @neeloong/form v0.4.0
2
+ * @neeloong/form v0.4.1
3
3
  * (c) 2024-2025 Fierflame
4
4
  * @license Apache-2.0
5
5
  */
@@ -79,7 +79,7 @@ declare namespace index_d {
79
79
 
80
80
  type VerifyError = any;
81
81
  type Component = {
82
- tag: string | ((ctx: any) => Element);
82
+ tag: string | ((ctx: any) => Element | [Element, (Element | null)?]);
83
83
  is?: string | undefined;
84
84
  attrs?: Record<string, Component.Attr> | undefined;
85
85
  events?: Record<string, Component.Event> | undefined;
@@ -156,25 +156,22 @@ declare namespace Schema {
156
156
  type?: null | undefined;
157
157
  props?: Record<string, Schema.Field> | undefined;
158
158
  array?: boolean | undefined;
159
- meta?: any;
160
159
  };
161
160
  type Type = {
162
161
  type: string;
163
162
  props?: null | undefined;
164
163
  array?: boolean | undefined;
165
- meta?: any;
166
164
  };
167
165
  type Event = {
168
- input?: Function | null | undefined;
169
- change?: Function | null | undefined;
170
- click?: Function | null | undefined;
171
- focus?: Function | null | undefined;
172
- blur?: Function | null | undefined;
173
- add?: Function | null | undefined;
174
- remove?: Function | null | undefined;
175
- move?: Function | null | undefined;
166
+ input: InputEvent;
167
+ change: InputEvent;
168
+ click: Event;
169
+ focus: Event;
170
+ blur: Event;
176
171
  };
177
172
  type Attr = {
173
+ meta?: any;
174
+ component?: any;
178
175
  immutable?: boolean | undefined;
179
176
  creatable?: boolean | undefined;
180
177
  hidden?: boolean | ((store: Store, root: Store) => boolean) | null | undefined;
@@ -210,6 +207,13 @@ declare namespace Schema {
210
207
  * 可选值
211
208
  */
212
209
  values?: (Schema.Value.Define | Schema.Value.Group.Define)[] | undefined;
210
+ events?: {
211
+ input?: ((this: Store, value: InputEvent, store: Store) => void | boolean | null) | null | undefined;
212
+ change?: ((this: Store, value: InputEvent, store: Store) => void | boolean | null) | null | undefined;
213
+ click?: ((this: Store, value: Event, store: Store) => void | boolean | null) | null | undefined;
214
+ focus?: ((this: Store, value: Event, store: Store) => void | boolean | null) | null | undefined;
215
+ blur?: ((this: Store, value: Event, store: Store) => void | boolean | null) | null | undefined;
216
+ } | undefined;
213
217
  };
214
218
  }
215
219
 
@@ -280,10 +284,30 @@ declare class Store<T = any> {
280
284
  onUpdate?: ((value: T | null, index: any) => void) | null | undefined;
281
285
  onUpdateState?: ((value: T | null, index: any) => void) | null | undefined;
282
286
  });
287
+ /**
288
+ *
289
+ * @template {keyof Schema.Event} K
290
+ * @param {K} event
291
+ * @param {Schema.Event[K]} value
292
+ */
293
+ emit<K extends keyof Schema.Event>(event: K, value: Schema.Event[K]): boolean;
294
+ /**
295
+ *
296
+ * @template {keyof Schema.Event} K
297
+ * @param {K} event
298
+ * @param {(this: this, p: Schema.Event[K], store: this) => void | boolean | null} listener
299
+ * @returns {() => void}
300
+ */
301
+ listen<K extends keyof Schema.Event>(event: K, listener: (this: this, p: Schema.Event[K], store: this) => void | boolean | null): () => void;
283
302
  get null(): boolean;
303
+ get kind(): string;
284
304
  schema: Schema.Field;
305
+ get store(): this;
285
306
  get parent(): Store<any> | null;
286
307
  get root(): Store<any>;
308
+ get type(): any;
309
+ get meta(): any;
310
+ get component(): any;
287
311
  set length(v: number);
288
312
  get length(): number;
289
313
  set index(v: string | number);
package/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * @neeloong/form v0.4.0
2
+ * @neeloong/form v0.4.1
3
3
  * (c) 2024-2025 Fierflame
4
4
  * @license Apache-2.0
5
5
  */
@@ -693,6 +693,43 @@
693
693
  * @template [T=any]
694
694
  */
695
695
  class Store {
696
+ /** @type {Map<string, Set<(value: any, store: any) => void | boolean | null>>} */
697
+ #events = new Map()
698
+ /**
699
+ *
700
+ * @template {keyof Schema.Event} K
701
+ * @param {K} event
702
+ * @param {Schema.Event[K]} value
703
+ */
704
+ emit(event, value) {
705
+ const key = typeof event === 'number' ? String(event) : event;
706
+ const events = this.#events;
707
+ let canceled = false;
708
+ for (const d of [...events.get(key) || []]) {
709
+ canceled = d(value, this) === false || canceled;
710
+ }
711
+ return !canceled;
712
+ }
713
+ /**
714
+ *
715
+ * @template {keyof Schema.Event} K
716
+ * @param {K} event
717
+ * @param {(this: this, p: Schema.Event[K], store: this) => void | boolean | null} listener
718
+ * @returns {() => void}
719
+ */
720
+ listen(event, listener) {
721
+ const fn = listener.bind(this);
722
+ const events = this.#events;
723
+ const key = typeof event === 'number' ? String(event) : event;
724
+ let set = events.get(key);
725
+ if (!set) {
726
+ set = new Set();
727
+ events.set(key, set);
728
+ }
729
+ set.add(fn);
730
+ return () => { set?.delete(fn); }
731
+
732
+ }
696
733
  /**
697
734
  * @param {Schema} schema
698
735
  * @param {object} [options]
@@ -703,6 +740,7 @@
703
740
  }
704
741
  #null = false;
705
742
  get null() { return this.#null; }
743
+ get kind() { return ''; }
706
744
  /**
707
745
  * @param {Schema.Field} schema
708
746
  * @param {object} options
@@ -747,6 +785,9 @@
747
785
  this.#root = parent.#root;
748
786
  // TODO: 事件向上冒泡
749
787
  }
788
+ this.#type = schema.type;
789
+ this.#meta = schema.meta;
790
+ this.#component = schema.component;
750
791
 
751
792
  const selfNewState = new exports.Signal.State(Boolean(isNew));
752
793
  this.#selfNew = selfNewState;
@@ -807,6 +848,13 @@
807
848
  this.#convert = typeof convert === 'function' ? convert : null;
808
849
  this.#length.set(length || 0);
809
850
  this.#index.set(index ?? '');
851
+
852
+ for (const [k, f] of Object.entries(schema.events || {})) {
853
+ if (typeof f !== 'function') { continue; }
854
+ // @ts-ignore
855
+ this.listen(k, f);
856
+ }
857
+
810
858
 
811
859
  }
812
860
  #destroyed = false;
@@ -824,8 +872,18 @@
824
872
  #parent = null;
825
873
  /** @readonly @type {Store} */
826
874
  #root = this;
875
+ /** @readonly @type {any} */
876
+ #type;
877
+ /** @readonly @type {any} */
878
+ #meta;
879
+ /** @readonly @type {any} */
880
+ #component;
881
+ get store() { return this; }
827
882
  get parent() { return this.#parent; }
828
883
  get root() { return this.#root; }
884
+ get type() { return this.#type; }
885
+ get meta() { return this.#meta; }
886
+ get component() { return this.#component; }
829
887
 
830
888
  #length = new exports.Signal.State(0);
831
889
  get length() { return this.#length.get(); }
@@ -1110,6 +1168,7 @@
1110
1168
 
1111
1169
 
1112
1170
  class ObjectStore extends Store {
1171
+ get kind() { return 'object'; }
1113
1172
  /** @type {Record<string, Store>} */
1114
1173
  #children
1115
1174
  *[Symbol.iterator]() {yield* Object.entries(this.#children);}
@@ -1200,6 +1259,7 @@
1200
1259
  }
1201
1260
  return children[Number(key)] || null;
1202
1261
  }
1262
+ get kind() { return 'array'; }
1203
1263
  /**
1204
1264
  * @param {Schema.Field} schema
1205
1265
  * @param {object} [options]
@@ -2324,8 +2384,24 @@
2324
2384
  }
2325
2385
 
2326
2386
  const bindable = {
2387
+ type: true,
2388
+ meta: true,
2389
+ component: true,
2390
+ kind: true,
2391
+
2392
+ value: true,
2393
+ state: true,
2394
+
2395
+ store: true,
2396
+ parent: true,
2397
+ root: true,
2398
+
2399
+ schema: true,
2400
+
2327
2401
  new: true,
2328
2402
  readonly: true,
2403
+ creatable: true,
2404
+ immutable: true,
2329
2405
 
2330
2406
  required: true,
2331
2407
  clearable: true,
@@ -2339,6 +2415,11 @@
2339
2415
  max: true,
2340
2416
  step: true,
2341
2417
  values: true,
2418
+
2419
+ null: true,
2420
+ index: true,
2421
+ no: true,
2422
+ length: true,
2342
2423
  };
2343
2424
  /** @type {Set<keyof typeof bindable>} */
2344
2425
  // @ts-ignore
@@ -2353,21 +2434,12 @@
2353
2434
  */
2354
2435
  function *toItem(val, key = '', sign = '$') {
2355
2436
  yield [`${key}`, {get: () => val.value, set: v => val.value = v, store: val}];
2356
- yield [`${key}${sign}value`, {get: () => val.value, set: v => val.value = v}];
2357
- yield [`${key}${sign}state`, {get: () => val.state, set: v => val.state = v}];
2358
- yield [`${key}${sign}store`, {get: () => val}];
2359
- yield [`${key}${sign}schema`, {get: () => val.schema}];
2360
- yield [`${key}${sign}null`, {get: () => val.null}];
2361
- yield [`${key}${sign}index`, {get: () => val.index}];
2362
- yield [`${key}${sign}no`, {get: () => val.no}];
2363
- yield [`${key}${sign}length`, {get: () => val.length}];
2364
- yield [`${key}${sign}creatable`, {get: () => val.creatable}];
2365
- yield [`${key}${sign}immutable`, {get: () => val.immutable}];
2366
-
2367
2437
 
2368
2438
  for (const k of bindableSet) {
2369
2439
  yield [`${key}${sign}${k}`, {get: () => val[k]}];
2370
2440
  }
2441
+ yield [`${key}${sign}value`, {get: () => val.value, set: v => val.value = v}];
2442
+ yield [`${key}${sign}state`, {get: () => val.state, set: v => val.state = v}];
2371
2443
  if (!(val instanceof ArrayStore)) { return; }
2372
2444
  yield [`${key}${sign}insert`, {exec: (index, value) => val.insert(index, value)}];
2373
2445
  yield [`${key}${sign}add`, {exec: (v) => val.add(v)}];
@@ -2547,10 +2619,11 @@
2547
2619
  const item = this.#items[name];
2548
2620
  if (!item?.get) { return; }
2549
2621
  const { store } = item;
2550
- if (!store) { return; }
2551
- switch(type) {
2552
- case 'value': return watch(() => store.value, cb);
2553
- case 'state': return watch(() => store.state, cb);
2622
+ if (!store) {
2623
+ switch(type) {
2624
+ case 'value': return watch(() => item.get(), cb);
2625
+ }
2626
+ return;
2554
2627
  }
2555
2628
  // @ts-ignore
2556
2629
  if (bindableSet.has(type)) {
@@ -2575,8 +2648,6 @@
2575
2648
  const res = Object.fromEntries([...bindableSet].map(v => [
2576
2649
  `$${v}`, cb => watch(() => store[v], cb)
2577
2650
  ]));
2578
- res.$value = cb => watch(() => store.value, cb);
2579
- res.$state = cb => watch(() => store.state, cb);
2580
2651
  return res;
2581
2652
  }
2582
2653
  /**
@@ -2596,7 +2667,7 @@
2596
2667
  }
2597
2668
  /**
2598
2669
  * @param {string} name
2599
- * @returns {Record<string, ((value: any) => void) | void> | void}
2670
+ * @returns {Record<string, (value: any) => void> | void}
2600
2671
  */
2601
2672
  bindStateAllSet(name) {
2602
2673
  const item = this.#items[name];
@@ -2612,6 +2683,27 @@
2612
2683
  '$state': v => {store.state = v; },
2613
2684
  }
2614
2685
  }
2686
+ /**
2687
+ * @param {string} name
2688
+ * @returns {Record<string, (value: any) => void> | void}
2689
+ */
2690
+ bindEvents(name) {
2691
+ const item = this.#items[name];
2692
+ if (!item?.get) { return; }
2693
+ const { store } = item;
2694
+ if (!store) {
2695
+ const set = item.set;
2696
+ if (typeof set !== 'function') { return; }
2697
+ return { '$value': set }
2698
+ }
2699
+ return {
2700
+ '$input': v => {store.emit('input', v); },
2701
+ '$change': v => {store.emit('change', v); },
2702
+ '$click': v => {store.emit('click', v); },
2703
+ '$focus': v => {store.emit('focus', v); },
2704
+ '$blur': v => {store.emit('blur', v); },
2705
+ }
2706
+ }
2615
2707
 
2616
2708
  /**
2617
2709
  * @param {string | Layout.EventListener} event
@@ -4058,12 +4150,13 @@
4058
4150
  */
4059
4151
  function renderItem(layout, parent, next, env, templates, componentPath, getComponent) {
4060
4152
  env = env.set(layout.aliases, layout.vars);
4153
+ const bind = layout.directives.bind;
4061
4154
  const fragment = layout.directives.fragment;
4062
4155
  if (fragment && typeof fragment === 'string') {
4063
4156
  const template = templates[fragment];
4064
4157
  if (!template) { return () => {}; }
4065
4158
  const [templateLayout, templateEnv] = template;
4066
- const newEnv = templateEnv.params(templateLayout, layout, env, layout.directives.bind);
4159
+ const newEnv = templateEnv.params(templateLayout, layout, env, bind);
4067
4160
  return render(templateLayout, parent, next, newEnv, templates, componentPath, getComponent);
4068
4161
  }
4069
4162
  if (!layout.name || layout.directives.fragment) {
@@ -4080,14 +4173,20 @@
4080
4173
 
4081
4174
  const componentAttrs = component?.attrs;
4082
4175
  const attrs = componentAttrs
4083
- ? bindAttrs(handler, env, layout.attrs, componentAttrs, layout.directives.bind)
4084
- : bindBaseAttrs(handler, env, layout.attrs, layout.directives.bind);
4176
+ ? bindAttrs(handler, env, layout.attrs, componentAttrs, bind)
4177
+ : bindBaseAttrs(handler, env, layout.attrs, bind);
4085
4178
 
4086
4179
  for (const [name, event] of Object.entries(layout.events)) {
4087
4180
  const fn = env.getEvent(event);
4088
4181
  if (fn) { handler.addEvent(name, fn); }
4089
4182
  }
4090
4183
 
4184
+ if (bind && typeof bind !== 'boolean') {
4185
+ for (const [key, event] of Object.entries(env.bindEvents(bind) || {})) {
4186
+ handler.addEvent(key, event);
4187
+ }
4188
+ }
4189
+
4091
4190
  const r = component ?
4092
4191
  typeof component.tag === 'function'
4093
4192
  ? component.tag(context)