@neeloong/form 0.4.1 → 0.4.3

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.4.1
2
+ * @neeloong/form v0.4.3
3
3
  * (c) 2024-2025 Fierflame
4
4
  * @license Apache-2.0
5
5
  */
@@ -119,9 +119,9 @@ class Store {
119
119
  #events = new Map()
120
120
  /**
121
121
  *
122
- * @template {keyof Schema.Event} K
122
+ * @template {keyof Schema.Events} K
123
123
  * @param {K} event
124
- * @param {Schema.Event[K]} value
124
+ * @param {Schema.Events[K]} value
125
125
  */
126
126
  emit(event, value) {
127
127
  const key = typeof event === 'number' ? String(event) : event;
@@ -134,9 +134,9 @@ class Store {
134
134
  }
135
135
  /**
136
136
  *
137
- * @template {keyof Schema.Event} K
137
+ * @template {keyof Schema.Events} K
138
138
  * @param {K} event
139
- * @param {(this: this, p: Schema.Event[K], store: this) => void | boolean | null} listener
139
+ * @param {(this: this, p: Schema.Events[K], store: this) => void | boolean | null} listener
140
140
  * @returns {() => void}
141
141
  */
142
142
  listen(event, listener) {
@@ -2033,12 +2033,12 @@ class Environment {
2033
2033
  }
2034
2034
 
2035
2035
  /**
2036
- * @param {string} name
2036
+ * @param {string | true} name
2037
2037
  * @param {string} type
2038
2038
  * @param {(value: any) => void} cb
2039
2039
  */
2040
2040
  bind(name, type, cb) {
2041
- const item = this.#items[name];
2041
+ const item = this.#items[name === true ? '' : name];
2042
2042
  if (!item?.get) { return; }
2043
2043
  const { store } = item;
2044
2044
  if (!store) {
@@ -2054,11 +2054,11 @@ class Environment {
2054
2054
  }
2055
2055
  }
2056
2056
  /**
2057
- * @param {string} name
2057
+ * @param {string | true} name
2058
2058
  * @returns {Record<string, ((cb: (value: any) => void) => () => void) | void> | void}
2059
2059
  */
2060
2060
  bindAll(name) {
2061
- const item = this.#items[name];
2061
+ const item = this.#items[name === true ? '' : name];
2062
2062
  if (!item?.get) { return; }
2063
2063
  const { store } = item;
2064
2064
  if (!store) {
@@ -2073,12 +2073,12 @@ class Environment {
2073
2073
  return res;
2074
2074
  }
2075
2075
  /**
2076
- * @param {string} name
2076
+ * @param {string | true} name
2077
2077
  * @param {string} type
2078
2078
  * @returns {((value: any) => void) | void}
2079
2079
  */
2080
2080
  bindSet(name, type) {
2081
- const item = this.#items[name];
2081
+ const item = this.#items[name === true ? '' : name];
2082
2082
  if (!item?.get) { return; }
2083
2083
  const { store } = item;
2084
2084
  if (!store) { return; }
@@ -2088,37 +2088,21 @@ class Environment {
2088
2088
  }
2089
2089
  }
2090
2090
  /**
2091
- * @param {string} name
2091
+ * @param {string | true} name
2092
2092
  * @returns {Record<string, (value: any) => void> | void}
2093
2093
  */
2094
- bindStateAllSet(name) {
2095
- const item = this.#items[name];
2094
+ bindEvents(name) {
2095
+ const item = this.#items[name === true ? '' : name];
2096
2096
  if (!item?.get) { return; }
2097
2097
  const { store } = item;
2098
2098
  if (!store) {
2099
2099
  const set = item.set;
2100
2100
  if (typeof set !== 'function') { return; }
2101
2101
  return { '$value': set }
2102
- }
2102
+ }
2103
2103
  return {
2104
2104
  '$value': v => {store.value = v; },
2105
2105
  '$state': v => {store.state = v; },
2106
- }
2107
- }
2108
- /**
2109
- * @param {string} name
2110
- * @returns {Record<string, (value: any) => void> | void}
2111
- */
2112
- bindEvents(name) {
2113
- const item = this.#items[name];
2114
- if (!item?.get) { return; }
2115
- const { store } = item;
2116
- if (!store) {
2117
- const set = item.set;
2118
- if (typeof set !== 'function') { return; }
2119
- return { '$value': set }
2120
- }
2121
- return {
2122
2106
  '$input': v => {store.emit('input', v); },
2123
2107
  '$change': v => {store.emit('change', v); },
2124
2108
  '$click': v => {store.emit('click', v); },
@@ -2474,7 +2458,7 @@ function bindAttrs(handler, envs, attrs, componentAttrs, bindValue) {
2474
2458
  continue;
2475
2459
  }
2476
2460
  const bind = attr.bind;
2477
- if (!bindValue || !bind || typeof bindValue === 'boolean') {
2461
+ if (!bindValue || !bind) {
2478
2462
  handler.set(name, attr.default);
2479
2463
  continue;
2480
2464
  }
@@ -2496,8 +2480,9 @@ function bindAttrs(handler, envs, attrs, componentAttrs, bindValue) {
2496
2480
  continue;
2497
2481
  }
2498
2482
  if (!isState) {
2499
- bk.add(envs.watch(bindValue, v => handler.set(name, v)));
2500
- handler.addEvent(event, (...args) => { envs.all[bindValue] = set(...args);});
2483
+ const bindKey = bindValue === true ? '' : bindValue;
2484
+ bk.add(envs.watch(bindKey, v => handler.set(name, v)));
2485
+ handler.addEvent(event, (...args) => { envs.all[bindKey] = set(...args);});
2501
2486
  continue;
2502
2487
  }
2503
2488
  const r = envs.bind(bindValue, 'state', v => handler.set(name, v));
@@ -2525,9 +2510,8 @@ function bindAttrs(handler, envs, attrs, componentAttrs, bindValue) {
2525
2510
  * @param {Component.Handler} handler
2526
2511
  * @param {Environment} envs
2527
2512
  * @param {Record<string, string | {name: string} | Layout.Calc>} attrs
2528
- * @param {string | boolean | null} [bindValue]
2529
2513
  */
2530
- function bindBaseAttrs(handler, envs, attrs, bindValue) {
2514
+ function bindBaseAttrs(handler, envs, attrs) {
2531
2515
  const tag = handler.tag;
2532
2516
  let bk = new Set();
2533
2517
  for (const [name, attr] of Object.entries(attrs)) {
@@ -2543,16 +2527,6 @@ function bindBaseAttrs(handler, envs, attrs, bindValue) {
2543
2527
  }
2544
2528
  bk.add(envs.watch(attrSchema, val => handler.set(name, val)));
2545
2529
  }
2546
- if (bindValue && typeof bindValue !== 'boolean') {
2547
- for (const [key, effect] of Object.entries(envs.bindAll(bindValue) || {})) {
2548
- if (typeof effect !== 'function') { continue; }
2549
- bk.add(effect(val => handler.set(key, val)));
2550
- }
2551
- for (const [key, setter] of Object.entries(envs.bindStateAllSet(bindValue) || {})) {
2552
- if (typeof setter !== 'function') { continue; }
2553
- handler.addEvent(key, $event => setter($event));
2554
- }
2555
- }
2556
2530
 
2557
2531
  return ()=> {
2558
2532
  const list = bk;
@@ -2745,7 +2719,6 @@ class EventEmitter {
2745
2719
 
2746
2720
  /** @import { Component } from '../types.mjs' */
2747
2721
  /** @import Environment from './Environment/index.mjs' */
2748
- /** @import Store from '../Store/index.mjs' */
2749
2722
 
2750
2723
 
2751
2724
  /** @type {Record<string, Component.Event.Filter>} */
@@ -2794,7 +2767,7 @@ const eventFilters = {
2794
2767
  if (evt instanceof KeyboardEvent) {
2795
2768
  const key = evt.code.toLowerCase().replace(/-/g, '');
2796
2769
  for (const k of param) {
2797
- if (key === k.toLowerCase().replace(/-/g, '')) { return true }
2770
+ if (key === k.toLowerCase().replace(/-/g, '')) { return true; }
2798
2771
  }
2799
2772
  return false;
2800
2773
  }
@@ -2833,34 +2806,34 @@ const eventFilters = {
2833
2806
  if (evt instanceof PointerEvent) {
2834
2807
  const pointerType = evt.pointerType.toLowerCase().replace(/-/g, '');
2835
2808
  for (const k of param) {
2836
- if (pointerType === k.toLowerCase().replace(/-/g, '')) { return true }
2809
+ if (pointerType === k.toLowerCase().replace(/-/g, '')) { return true; }
2837
2810
  }
2838
2811
  return false;
2839
2812
  }
2840
2813
  },
2841
2814
 
2842
2815
  ctrl(evt) {
2843
- if (evt instanceof MouseEvent|| evt instanceof KeyboardEvent || evt instanceof TouchEvent) {
2816
+ if (evt instanceof MouseEvent || evt instanceof KeyboardEvent || evt instanceof TouchEvent) {
2844
2817
  return evt.ctrlKey;
2845
2818
  }
2846
2819
  },
2847
2820
  alt(evt) {
2848
- if (evt instanceof MouseEvent|| evt instanceof KeyboardEvent || evt instanceof TouchEvent) {
2821
+ if (evt instanceof MouseEvent || evt instanceof KeyboardEvent || evt instanceof TouchEvent) {
2849
2822
  return evt.altKey;
2850
2823
  }
2851
2824
  },
2852
2825
  shift(evt) {
2853
- if (evt instanceof MouseEvent|| evt instanceof KeyboardEvent || evt instanceof TouchEvent) {
2826
+ if (evt instanceof MouseEvent || evt instanceof KeyboardEvent || evt instanceof TouchEvent) {
2854
2827
  return evt.shiftKey;
2855
2828
  }
2856
2829
  },
2857
2830
  meta(evt) {
2858
- if (evt instanceof MouseEvent|| evt instanceof KeyboardEvent || evt instanceof TouchEvent) {
2831
+ if (evt instanceof MouseEvent || evt instanceof KeyboardEvent || evt instanceof TouchEvent) {
2859
2832
  return evt.metaKey;
2860
2833
  }
2861
2834
  },
2862
2835
  cmd(evt) {
2863
- if (evt instanceof MouseEvent|| evt instanceof KeyboardEvent || evt instanceof TouchEvent) {
2836
+ if (evt instanceof MouseEvent || evt instanceof KeyboardEvent || evt instanceof TouchEvent) {
2864
2837
  return evt.ctrlKey || evt.metaKey;
2865
2838
  }
2866
2839
  },
@@ -2873,25 +2846,45 @@ const eventFilters = {
2873
2846
  */
2874
2847
  function createContext(component, env) {
2875
2848
  const tag = typeof component === 'string' ? component : component.tag;
2876
- const { attrs, events } = typeof component !== 'string' && component || {attrs: null, events: null };
2849
+ const { attrs, events } = typeof component !== 'string' && component || { attrs: null, events: null };
2877
2850
 
2878
2851
  let destroyed = false;
2879
- let init = false;
2852
+ const destroyedState = new Signal.State(false);
2853
+ let mounted = false;
2854
+ const mountedState = new Signal.State(false);
2855
+ /** @type {Record<string, Signal.State<any> | void>} */
2856
+ const attrStates = Object.create(null);
2880
2857
  const tagAttrs = Object.create(null);
2881
2858
 
2859
+ const attrWatchers = new Set();
2860
+
2882
2861
  /** @type {[string, ($event: any) => void, AddEventListenerOptions][]} */
2883
2862
  const allEvents = [];
2884
2863
  const stateEmitter = new EventEmitter();
2885
- /** @type {EventEmitter<Record<string, [any, any, string]>>} */
2886
- const attrEmitter = new EventEmitter();
2887
2864
  /** @type {Component.Context} */
2888
2865
  const context = {
2889
2866
  events: allEvents,
2890
- props: attrs ? new Set(Object.entries(attrs).filter(([,a]) => a.isProp).map(([e]) => e)) : null,
2867
+ props: attrs ? new Set(Object.entries(attrs).filter(([, a]) => a.isProp).map(([e]) => e)) : null,
2891
2868
  tagAttrs,
2892
- watchAttr(name, fn) { return attrEmitter.listen(name, fn); },
2893
- get destroyed() { return destroyed},
2894
- get init() { return init},
2869
+ watchAttr(name, fn) {
2870
+ if (destroyed) { return () => { }; }
2871
+ const state = attrStates[name];
2872
+ if (!state) { return () => { }; }
2873
+ let old = state.get();
2874
+ const w = watch(() => state.get(), v => {
2875
+ const o = old;
2876
+ old = v;
2877
+ fn(v, o, name);
2878
+ });
2879
+ attrWatchers.add(w);
2880
+
2881
+ return () => {
2882
+ attrWatchers.delete(w);
2883
+ w();
2884
+ };
2885
+ },
2886
+ get destroyed() { return destroyedState.get(); },
2887
+ get init() { return mountedState.get(); },
2895
2888
  listen(name, listener) { return stateEmitter.listen(name, listener); },
2896
2889
  };
2897
2890
  /** @type {Component.Handler} */
@@ -2899,11 +2892,18 @@ function createContext(component, env) {
2899
2892
  tag,
2900
2893
  set(name, value) {
2901
2894
  if (attrs && !(name in attrs)) { return; }
2902
- if (!(name in tagAttrs)) { tagAttrs[name] = void 0; }
2903
- const old = tagAttrs[name];
2904
- if (old === value) { return; }
2905
- tagAttrs[name] = value;
2906
- attrEmitter.emit(name, value, old, name);
2895
+ let state = attrStates[name];
2896
+ if (state) {
2897
+ state.set(value);
2898
+ return;
2899
+ }
2900
+ const s = new Signal.State(value);
2901
+ attrStates[name] = s;
2902
+ Object.defineProperty(tagAttrs, name, {
2903
+ configurable: true,
2904
+ enumerable: true,
2905
+ get: s.get.bind(s),
2906
+ });
2907
2907
  },
2908
2908
  addEvent(name, fn) {
2909
2909
  if (typeof fn !== 'function') { return; }
@@ -2914,14 +2914,14 @@ function createContext(component, env) {
2914
2914
  const options = {};
2915
2915
  /** @type {[Component.Event.Filter, string[], boolean][]} */
2916
2916
  const filterFns = [];
2917
- if (filters) for (let f = fs.shift();f;f = fs.shift()) {
2917
+ if (filters) for (let f = fs.shift(); f; f = fs.shift()) {
2918
2918
  const paramIndex = f.indexOf(':');
2919
2919
  const noParamName = paramIndex >= 0 ? f.slice(0, paramIndex) : f;
2920
2920
  const param = paramIndex >= 0 ? f.slice(paramIndex + 1).split(':') : [];
2921
2921
  const filterName = noParamName.replace(/^-+/, '');
2922
2922
  const sub = (noParamName.length - filterName.length) % 2 === 1;
2923
2923
  let filter = filters[filterName] || filterName;
2924
- switch(filter) {
2924
+ switch (filter) {
2925
2925
  case 'once':
2926
2926
  options.once = !sub;
2927
2927
  break;
@@ -2942,20 +2942,25 @@ function createContext(component, env) {
2942
2942
  allEvents.push([e, $event => {
2943
2943
  const global = env.all;
2944
2944
  for (const [filter, param, sub] of filterFns) {
2945
- if (filter($event, param, global) === sub) { return}
2945
+ if (filter($event, param, global) === sub) { return; }
2946
2946
  }
2947
2947
  fn($event, global);
2948
2948
  }, options]);
2949
2949
  },
2950
2950
  destroy() {
2951
- if (destroyed) { return }
2951
+ if (destroyed) { return; }
2952
2952
  destroyed = true;
2953
+ destroyedState.set(true);
2954
+ for (const w of attrWatchers) {
2955
+ w();
2956
+ }
2953
2957
  stateEmitter.emit('destroy');
2954
2958
  },
2955
- init() {
2956
- if (init) { return }
2957
- init = true;
2958
- stateEmitter.emit('init', {events: allEvents});
2959
+ mount() {
2960
+ if (mounted) { return; }
2961
+ mounted = true;
2962
+ mountedState.set(true);
2963
+ stateEmitter.emit('init', { events: allEvents });
2959
2964
  },
2960
2965
 
2961
2966
  };
@@ -3559,6 +3564,34 @@ function renderEnum(parent, next, getter, env, renderItem) {
3559
3564
  };
3560
3565
  }
3561
3566
 
3567
+ /** @import { Component } from '../types.mjs' */
3568
+
3569
+
3570
+ /**
3571
+ * @param {Component.Handler} handler
3572
+ * @param {Environment} env
3573
+ * @param {string | boolean | null} [bind]
3574
+ */
3575
+ function bindBase(handler, env, bind) {
3576
+ if (!bind) { return () => {}; }
3577
+ let bk = new Set();
3578
+ for (const [key, effect] of Object.entries(env.bindAll(bind) || {})) {
3579
+ if (typeof effect !== 'function') { continue; }
3580
+ bk.add(effect(val => handler.set(key, val)));
3581
+ }
3582
+ for (const [key, setter] of Object.entries(env.bindEvents(bind) || {})) {
3583
+ handler.addEvent(key, $event => setter($event));
3584
+ }
3585
+
3586
+ return ()=> {
3587
+ const list = bk;
3588
+ bk = new Set();
3589
+ for (const s of list) {
3590
+ s();
3591
+ }
3592
+ }
3593
+ }
3594
+
3562
3595
  /** @import Store from '../Store/index.mjs' */
3563
3596
 
3564
3597
  /**
@@ -3568,7 +3601,7 @@ function renderEnum(parent, next, getter, env, renderItem) {
3568
3601
  * @param {Environment} env
3569
3602
  * @param {Record<string, [Layout.Node, Environment]>} templates
3570
3603
  * @param {string[]} componentPath
3571
- * @param {((path: string[]) => Component?)?} [getComponent]
3604
+ * @param {Component.Getter?} [getComponent]
3572
3605
  */
3573
3606
  function renderItem(layout, parent, next, env, templates, componentPath, getComponent) {
3574
3607
  env = env.set(layout.aliases, layout.vars);
@@ -3596,18 +3629,15 @@ function renderItem(layout, parent, next, env, templates, componentPath, getComp
3596
3629
  const componentAttrs = component?.attrs;
3597
3630
  const attrs = componentAttrs
3598
3631
  ? bindAttrs(handler, env, layout.attrs, componentAttrs, bind)
3599
- : bindBaseAttrs(handler, env, layout.attrs, bind);
3632
+ : bindBaseAttrs(handler, env, layout.attrs);
3600
3633
 
3601
3634
  for (const [name, event] of Object.entries(layout.events)) {
3602
3635
  const fn = env.getEvent(event);
3603
3636
  if (fn) { handler.addEvent(name, fn); }
3604
3637
  }
3605
3638
 
3606
- if (bind && typeof bind !== 'boolean') {
3607
- for (const [key, event] of Object.entries(env.bindEvents(bind) || {})) {
3608
- handler.addEvent(key, event);
3609
- }
3610
- }
3639
+ const base = bindBase(handler, env, bind);
3640
+
3611
3641
 
3612
3642
  const r = component ?
3613
3643
  typeof component.tag === 'function'
@@ -3627,13 +3657,14 @@ function renderItem(layout, parent, next, env, templates, componentPath, getComp
3627
3657
  bindClasses(root, layout.classes, env);
3628
3658
  bindStyles(root, layout.styles, env);
3629
3659
 
3630
- handler.init();
3660
+ handler.mount();
3631
3661
 
3632
3662
  return () => {
3633
3663
  root.remove();
3634
3664
  handler.destroy();
3635
3665
  attrs();
3636
3666
  children();
3667
+ base();
3637
3668
  };
3638
3669
  }
3639
3670
  /**
@@ -3644,7 +3675,7 @@ function renderItem(layout, parent, next, env, templates, componentPath, getComp
3644
3675
  * @param {Environment} env
3645
3676
  * @param {Record<string, [Layout.Node, Environment]>} templates
3646
3677
  * @param {string[]} componentPath
3647
- * @param {((path: string[]) => Component?)?} [getComponent]
3678
+ * @param {Component.Getter?} [getComponent]
3648
3679
  * @returns {() => void}
3649
3680
  */
3650
3681
  function render(layout, parent, next, env, templates, componentPath, getComponent) {
@@ -3683,15 +3714,15 @@ function render(layout, parent, next, env, templates, componentPath, getComponen
3683
3714
  * @param {Store} store
3684
3715
  * @param {(Layout.Node | string)[]} layouts
3685
3716
  * @param {Element} parent
3686
- * @param {((path: string[]) => Component?)?} [components]
3717
+ * @param {Component.Getter?} [components]
3687
3718
  * @returns {() => void}
3688
3719
  */
3689
3720
  /**
3690
3721
  * @param {Store} store
3691
3722
  * @param {(Layout.Node | string)[]} layouts
3692
3723
  * @param {Element} parent
3693
- * @param {((path: string[]) => Component?) | Record<string, Store | {get?(): any; set?(v: any): void; exec?(...p: any[]): any; calc?(...p: any[]): any }> | null} [opt1]
3694
- * @param {((path: string[]) => Component?) | Record<string, Store | {get?(): any; set?(v: any): void; exec?(...p: any[]): any; calc?(...p: any[]): any }> | null} [opt2]
3724
+ * @param {Component.Getter | Record<string, Store | {get?(): any; set?(v: any): void; exec?(...p: any[]): any; calc?(...p: any[]): any }> | null} [opt1]
3725
+ * @param {Component.Getter | Record<string, Store | {get?(): any; set?(v: any): void; exec?(...p: any[]): any; calc?(...p: any[]): any }> | null} [opt2]
3695
3726
  */
3696
3727
  function index (store, layouts, parent, opt1, opt2) {
3697
3728
  const options = [opt1, opt2];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@neeloong/form",
3
- "version": "0.4.1",
3
+ "version": "0.4.3",
4
4
  "description": "",
5
5
  "keywords": [
6
6
  "from",