@neeloong/form 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -160,12 +160,13 @@ render(store, layouts, app);
160
160
 
161
161
  优先级从高到低:
162
162
 
163
- 1. 片段标志: `!fragment`
163
+ 1. 模板定义: `!template`
164
164
  1. 条件: `!if` `!else`
165
165
  1. 子属性: `!value`
166
166
  1. 枚举: `!enum`
167
167
  1. 别名与计算名: `*别名` `*计算名`
168
168
  1. 显式变量: `+变量`
169
+ 1. 片段与模板调用: `!fragment`
169
170
  1. 属性与事件: `:绑定属性` `@事件` `普通属性` `!bind`
170
171
  1. 子内容: `!text` `!html`
171
172
  1. 注释: `!comment`
package/index.d.mts CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * @neeloong/form v0.2.0
2
+ * @neeloong/form v0.3.0
3
3
  * (c) 2024-2025 Fierflame
4
4
  * @license Apache-2.0
5
5
  */
@@ -325,7 +325,8 @@ declare function parse(source: string, { creteCalc, creteEvent, simpleTag, }?: O
325
325
  declare function toString(value: Node | (Node | string)[], formable?: boolean): string;
326
326
 
327
327
  type Directives = {
328
- fragment?: boolean | undefined;
328
+ template?: string | undefined;
329
+ fragment?: string | boolean | undefined;
329
330
  if?: string | Function | undefined;
330
331
  else?: boolean | undefined;
331
332
  /**
@@ -356,6 +357,7 @@ type Node = {
356
357
  attrs: Record<string, string | {
357
358
  name: string;
358
359
  } | ((global: any) => void)>;
360
+ params: Record<string, string | ((global: any) => void)>;
359
361
  classes: Record<string, string | boolean | ((global: any) => void)>;
360
362
  styles: Record<string, string | ((global: any) => void)>;
361
363
  events: Record<string, string | (($event: any, global: any) => void)>;
package/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * @neeloong/form v0.2.0
2
+ * @neeloong/form v0.3.0
3
3
  * (c) 2024-2025 Fierflame
4
4
  * @license Apache-2.0
5
5
  */
@@ -1478,7 +1478,7 @@
1478
1478
 
1479
1479
  /** @import * as Layout from './index.mjs' */
1480
1480
 
1481
- const attrPattern = /^(?<decorator>[:@!+*\.]|style:|样式:)?(?<name>-?[\w\p{Unified_Ideograph}_][-\w\p{Unified_Ideograph}_:\d\.]*)$/u;
1481
+ const attrPattern = /^(?<decorator>[:@!+*\.?]|style:|样式:)?(?<name>-?[\w\p{Unified_Ideograph}_][-\w\p{Unified_Ideograph}_:\d\.]*)$/u;
1482
1482
  const nameRegex = /^(?<name>[a-zA-Z$\p{Unified_Ideograph}_][\da-zA-Z$\p{Unified_Ideograph}_]*)?$/u;
1483
1483
  /**
1484
1484
  * @param {Layout.Node} node
@@ -1486,7 +1486,7 @@
1486
1486
  * @param {Exclude<Layout.Options['creteEvent'], undefined>} creteEvent
1487
1487
  */
1488
1488
  function createAttributeAdder(node, creteCalc, creteEvent) {
1489
- const { attrs, directives, events, classes, styles, vars, aliases } = node;
1489
+ const { attrs, directives, events, classes, styles, vars, aliases, params } = node;
1490
1490
  /**
1491
1491
  * @param {string} qName
1492
1492
  * @param {string} value
@@ -1517,13 +1517,13 @@
1517
1517
  vars[name] = !value ? '' : nameRegex.test(value) ? value : creteCalc(value);
1518
1518
  } else if (decorator === '*') {
1519
1519
  aliases[name] = nameRegex.test(value) ? value : creteCalc(value);
1520
+ } else if (decorator === '?') {
1521
+ params[name] = nameRegex.test(value) ? value : creteCalc(value);
1520
1522
  } else if (decorator === '!') {
1521
1523
  const key = name.toString();
1522
1524
  switch (key) {
1523
- case 'fragment':
1524
- case 'else':
1525
- directives[key] = true;
1526
- break;
1525
+ case 'fragment': directives.fragment = value || true; break;
1526
+ case 'else': directives.else = true; break;
1527
1527
  case 'enum':
1528
1528
  directives.enum = value ? nameRegex.test(value) ? value : creteCalc(value) : true;
1529
1529
  break;
@@ -1532,6 +1532,7 @@
1532
1532
  case 'html':
1533
1533
  directives[key] = nameRegex.test(value) ? value : creteCalc(value);
1534
1534
  break;
1535
+ case 'template':
1535
1536
  case 'bind':
1536
1537
  case 'value':
1537
1538
  case 'comment':
@@ -1563,6 +1564,7 @@
1563
1564
  styles: Object.create(null),
1564
1565
  vars: Object.create(null),
1565
1566
  aliases: Object.create(null),
1567
+ params: Object.create(null),
1566
1568
  };
1567
1569
  }
1568
1570
 
@@ -2209,7 +2211,9 @@
2209
2211
  /**
2210
2212
  * @typedef {object} Directives
2211
2213
  *
2212
- * @property {boolean} [fragment]
2214
+ * @property {string} [template]
2215
+ *
2216
+ * @property {boolean | string} [fragment]
2213
2217
  *
2214
2218
  * @property {string | Function} [if]
2215
2219
  * @property {boolean} [else]
@@ -2238,6 +2242,7 @@
2238
2242
  * @property {string?} [is]
2239
2243
  * @property {string} [id]
2240
2244
  * @property {Record<string, string | {name: string} | ((global: any) => void)>} attrs
2245
+ * @property {Record<string, string | ((global: any) => void)>} params
2241
2246
  * @property {Record<string, string | boolean | ((global: any) => void)>} classes
2242
2247
  * @property {Record<string, string | ((global: any) => void)>} styles
2243
2248
  * @property {Record<string, string | (($event: any, global: any) => void)>} events
@@ -2298,6 +2303,8 @@
2298
2303
  return () => { w.unwatch(computed); };
2299
2304
  }
2300
2305
 
2306
+ /** @import * as Layout from '../Layout/index.mjs' */
2307
+
2301
2308
  const bindable = {
2302
2309
  new: true,
2303
2310
  readonly: true,
@@ -2621,6 +2628,67 @@
2621
2628
  }
2622
2629
  return cloned;
2623
2630
  }
2631
+ /**
2632
+ *
2633
+ * @param {Layout.Node} template
2634
+ * @param {Layout.Node} source
2635
+ * @param {Environment} sourceEnv
2636
+ */
2637
+ params({params}, {attrs}, sourceEnv) {
2638
+ if (Object.keys(params).length === 0) { return this; }
2639
+ const cloned = new Environment(this);
2640
+ cloned.#store = this.#store;
2641
+ cloned.#parent = this.#parent;
2642
+ cloned.#object = this.#object;
2643
+ const explicit = cloned.#explicit;
2644
+ const items = cloned.#items;
2645
+ for (const [key, param] of Object.entries(params)) {
2646
+ const attr = key in attrs ? attrs[key] : null;
2647
+ if (typeof attr === 'string') {
2648
+ explicit[key] = items[key] = {get: () => attr};
2649
+ } else if (attr && typeof attr === 'object') {
2650
+ const item = sourceEnv.#items[attr.name];
2651
+ if (!item?.get) { continue; }
2652
+ if (!item.store) {
2653
+ explicit[key] = items[key] = item;
2654
+ continue;
2655
+ }
2656
+ for (const [k, it] of toItem(item.store, key)) {
2657
+ explicit[k] = items[k] = it;
2658
+ }
2659
+ continue;
2660
+
2661
+ } else if (typeof attr === 'function') {
2662
+ const val = new exports.Signal.Computed(() => attr(sourceEnv.getters));
2663
+ explicit[key] = items[key] = {
2664
+ get: () => { return val.get(); },
2665
+ };
2666
+
2667
+ continue;
2668
+ } else if (typeof param === 'function') {
2669
+ const getters = cloned.getters;
2670
+ cloned.#getters = null;
2671
+ const val = new exports.Signal.Computed(() => param(getters));
2672
+ explicit[key] = items[key] = {
2673
+ get: () => { return val.get(); },
2674
+ };
2675
+ continue;
2676
+
2677
+ } else {
2678
+ const item = items[param];
2679
+ if (!item?.get) { continue; }
2680
+ if (!item.store) {
2681
+ explicit[key] = items[key] = item;
2682
+ continue;
2683
+ }
2684
+ for (const [k, it] of toItem(item.store, key)) {
2685
+ explicit[k] = items[k] = it;
2686
+ }
2687
+ continue;
2688
+ }
2689
+ }
2690
+ return cloned;
2691
+ }
2624
2692
  /** @param {Record<string, any>} object */
2625
2693
  setObject(object) {
2626
2694
  const cloned = new Environment(this);
@@ -3670,15 +3738,24 @@
3670
3738
  * @param {Element} parent
3671
3739
  * @param {Node?} next
3672
3740
  * @param {Environment} envs
3673
- * @param {(layout: Layout.Node) => () => void} renderItem
3741
+ * @param {Record<string, [Layout.Node, Environment]>} templates
3742
+ * @param {(layout: Layout.Node, templates: Record<string, any>) => () => void} renderItem
3674
3743
  * @returns {() => void}
3675
3744
  */
3676
- function renderList(layouts, parent, next, envs, renderItem) {
3745
+ function renderList(layouts, parent, next, envs, templates, renderItem) {
3677
3746
 
3678
3747
  /** @type {Set<() => void>?} */
3679
3748
  let bkList = new Set();
3680
3749
  /** @type {[string | Function | null, Layout.Node][]} */
3681
3750
  let ifList = [];
3751
+ /** @type {Record<string, [Layout.Node, Environment]>} */
3752
+ let currentTemplates = Object.create(templates);
3753
+ for (const layout of layouts) {
3754
+ if (typeof layout === 'string') { continue; }
3755
+ const name = layout.directives.template;
3756
+ if (!name) { continue; }
3757
+ currentTemplates[name] = [layout, envs];
3758
+ }
3682
3759
 
3683
3760
  /** @param {[string | Function | null, Layout.Node][]} list */
3684
3761
  function renderIf(list) {
@@ -3696,7 +3773,7 @@
3696
3773
  function renderIndex(index) {
3697
3774
  const layout = list[index]?.[1];
3698
3775
  if (!layout) { return; }
3699
- destroy = renderItem(layout);
3776
+ destroy = renderItem(layout, currentTemplates);
3700
3777
  }
3701
3778
  bkList.add(() => {
3702
3779
  destroy();
@@ -3723,6 +3800,11 @@
3723
3800
  bkList.add(() => node.remove());
3724
3801
  continue;
3725
3802
  }
3803
+ if (layout.directives.template) {
3804
+ renderIf(ifList);
3805
+ ifList = [];
3806
+ continue;
3807
+ }
3726
3808
  if (ifList.length && layout.directives.else) {
3727
3809
  const ifv = layout.directives.if || null;
3728
3810
  ifList.push([ifv, layout]);
@@ -3740,7 +3822,7 @@
3740
3822
  continue;
3741
3823
  }
3742
3824
  bkList.add(
3743
- renderItem(layout)
3825
+ renderItem(layout, currentTemplates)
3744
3826
  );
3745
3827
  }
3746
3828
 
@@ -3870,34 +3952,31 @@
3870
3952
 
3871
3953
  /** @import Store from '../Store/index.mjs' */
3872
3954
 
3873
- /**
3874
- * @param {Layout.Node} layout
3875
- * @param {Element} parent
3876
- * @param {Node?} next
3877
- * @param {Environment} env
3878
- * @param {(layout: Layout.Node) => () => void} renderItem
3879
- * @returns {() => void}
3880
- */
3881
- function renderFragment(layout, parent, next, env, renderItem) {
3882
- return renderFillDirectives(parent, next, env, layout.directives) ||
3883
- renderList(layout.children || [], parent, next, env, renderItem);
3884
-
3885
- }
3886
3955
  /**
3887
3956
  * @param {Layout.Node} layout
3888
3957
  * @param {Element} parent
3889
3958
  * @param {Node?} next
3890
3959
  * @param {Store} store
3891
3960
  * @param {Environment} env
3961
+ * @param {Record<string, [Layout.Node, Environment]>} templates
3892
3962
  * @param {string[]} componentPath
3893
3963
  * @param {((path: string[]) => Component?)?} [getComponent]
3894
3964
  */
3895
- function renderItem(layout, parent, next, store, env, componentPath, getComponent) {
3965
+ function renderItem(layout, parent, next, store, env, templates, componentPath, getComponent) {
3896
3966
  env = env.set(layout.aliases, layout.vars);
3967
+ const fragment = layout.directives.fragment;
3968
+ if (fragment && typeof fragment === 'string') {
3969
+ const template = templates[fragment];
3970
+ if (!template) { return () => {}; }
3971
+ const [templateLayout, templateEnv] = template;
3972
+ const newEnv = templateEnv.params(templateLayout, layout, env);
3973
+ return render(templateLayout, parent, next, store, newEnv, templates, componentPath, getComponent);
3974
+ }
3897
3975
  if (!layout.name || layout.directives.fragment) {
3898
- return renderFragment(layout, parent, next, env, l => {
3899
- return render(l, parent, next, store, env, componentPath, getComponent);
3900
- });
3976
+ return renderFillDirectives(parent, next, env, layout.directives) ||
3977
+ renderList(layout.children || [], parent, next, env, templates, (layout, templates) => {
3978
+ return render(layout, parent, next, store, env, templates, componentPath, getComponent);
3979
+ });
3901
3980
  }
3902
3981
  const path = [...componentPath, layout.name];
3903
3982
  const component = getComponent?.(path);
@@ -3921,13 +4000,13 @@
3921
4000
  : createTagComponent(context, component.tag, component.is)
3922
4001
  : createTagComponent(context, layout.name, layout.is);
3923
4002
  const root = Array.isArray(r) ? r[0] : r;
3924
- const slot = Array.isArray(r) && r[1] || root;
4003
+ const slot = Array.isArray(r) ? r[1] : root;
3925
4004
  parent.insertBefore(root, next);
3926
- const children =
4005
+ const children = slot ?
3927
4006
  renderFillDirectives(slot, null, env, layout.directives)
3928
- || renderList(layout.children || [], slot, null, env, l => {
3929
- return render(l, slot, null, store, env, componentPath, getComponent);
3930
- });
4007
+ || renderList(layout.children || [], slot, null, env, templates, (layout, templates) => {
4008
+ return render(layout, slot, null, store, env, templates, componentPath, getComponent);
4009
+ }) : () => {};
3931
4010
 
3932
4011
 
3933
4012
  bindClasses(root, layout.classes, env);
@@ -3949,11 +4028,12 @@
3949
4028
  * @param {Node?} next
3950
4029
  * @param {Store} store
3951
4030
  * @param {Environment} env
4031
+ * @param {Record<string, [Layout.Node, Environment]>} templates
3952
4032
  * @param {string[]} componentPath
3953
4033
  * @param {((path: string[]) => Component?)?} [getComponent]
3954
4034
  * @returns {() => void}
3955
4035
  */
3956
- function render(layout, parent, next, store, env, componentPath, getComponent) {
4036
+ function render(layout, parent, next, store, env, templates, componentPath, getComponent) {
3957
4037
  const { directives } = layout;
3958
4038
  const { value } = directives;
3959
4039
  if (value) {
@@ -3964,22 +4044,22 @@
3964
4044
  }
3965
4045
  const enumValue = directives.enum;
3966
4046
  if (!enumValue) {
3967
- return renderItem(layout, parent, next, store, env, componentPath, getComponent);
4047
+ return renderItem(layout, parent, next, store, env, templates, componentPath, getComponent);
3968
4048
  }
3969
4049
  const newStore = enumValue === true ? store : env.enum(enumValue);
3970
4050
  if (newStore instanceof ArrayStore) {
3971
4051
  return renderArray(layout, parent, next, newStore, env, (a, b, c, store, env) => {
3972
- return renderItem(a, b, c, store, env, componentPath, getComponent);
4052
+ return renderItem(a, b, c, store, env, templates, componentPath, getComponent);
3973
4053
  });
3974
4054
  }
3975
4055
  if (newStore instanceof ObjectStore) {
3976
4056
  return renderObject(layout, parent, next, newStore, env, (a, b, c, store, env) => {
3977
- return renderItem(a, b, c, store, env, componentPath, getComponent);
4057
+ return renderItem(a, b, c, store, env, templates, componentPath, getComponent);
3978
4058
  });
3979
4059
  }
3980
4060
  if (typeof newStore === 'function') {
3981
4061
  return renderEnum(layout, parent, next, store, newStore, env, (a, b, c, store, env) => {
3982
- return renderItem(a, b, c, store, env, componentPath, getComponent);
4062
+ return renderItem(a, b, c, store, env, templates, componentPath, getComponent);
3983
4063
  });
3984
4064
  }
3985
4065
  return () => { };
@@ -4014,8 +4094,9 @@
4014
4094
  const components = options.find(v => typeof v === 'function');
4015
4095
  const global = options.find(v => typeof v === 'object');
4016
4096
  const env = new Environment(global).setStore(store);
4017
- return renderList(layouts, parent, null, env, l => {
4018
- return render(l, parent, null, store, env, [], components);
4097
+ const templates = Object.create(null);
4098
+ return renderList(layouts, parent, null, env, templates, (layout, templates) => {
4099
+ return render(layout, parent, null, store, env, templates, [], components);
4019
4100
  });
4020
4101
  }
4021
4102