@neeloong/form 0.1.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/index.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * @neeloong/form v0.1.0
2
+ * @neeloong/form v0.3.0
3
3
  * (c) 2024-2025 Fierflame
4
4
  * @license Apache-2.0
5
5
  */
@@ -13,14 +13,21 @@ export { Signal } from 'signal-polyfill';
13
13
  *
14
14
  * @param {Store} self
15
15
  * @param {boolean?} [defState]
16
- * @param {boolean | ((store: Store) => boolean) | null} [fn]
16
+ * @param {boolean | ((store: Store, root: Store) => boolean?) | null} [fn]
17
17
  * @param {Signal.Computed<boolean>?} [parent]
18
18
  * @returns {[Signal.State<boolean?>, Signal.Computed<boolean>]}
19
19
  */
20
20
  const createBooleanStates = (self, defState, fn, parent) => {
21
21
 
22
22
  const selfState = new Signal.State(typeof defState === 'boolean' ? defState : null);
23
- const scriptState = typeof fn === 'function' ? new Signal.Computed(() => fn(self)) : fn ? new Signal.Computed(() => true) : new Signal.Computed(() => false);
23
+ /** @type {Signal.Computed<boolean>} */
24
+ let scriptState;
25
+ if (typeof fn === 'function') {
26
+ scriptState = new Signal.Computed(() => Boolean(fn(self, self.root)));
27
+ } else {
28
+ const def = Boolean(fn);
29
+ scriptState = new Signal.Computed(() => def);
30
+ }
24
31
 
25
32
  const getState = () => {
26
33
  const s = selfState.get();
@@ -34,6 +41,75 @@ const createBooleanStates = (self, defState, fn, parent) => {
34
41
 
35
42
  };
36
43
 
44
+ /** @import { Schema } from '../types.mjs' */
45
+ /** @param {*} v */
46
+ const string = v => typeof v === 'string' && v || null;
47
+ /** @param {*} v */
48
+ const number = v => typeof v === 'number' && v || null;
49
+
50
+ /** @type {(v: Schema.Value | Schema.Value.Group | null) => v is Schema.Value | Schema.Value.Group} */
51
+ const valueFilter = /** @type {*} */(Boolean);
52
+ /**
53
+ *
54
+ * @param {*} v
55
+ * @returns {Schema.Value | Schema.Value.Group | null}
56
+ */
57
+ function toValueItem(v) {
58
+ if (typeof v === 'number' || typeof v === 'string') {
59
+ return /** @type {Schema.Value} */({ label: v, value: v});
60
+ }
61
+ if (!v || typeof v !== 'object') { return null; }
62
+ const {children, label, value} = v;
63
+ const list = Array.isArray(children) ? children.map(toValueItem).filter(valueFilter) : [];
64
+ if (list.length) {
65
+ return {children: list, label, value}
66
+ }
67
+ if (typeof value === 'number' || typeof value === 'string') {
68
+ return {label, value};
69
+ }
70
+ return null;
71
+
72
+ }
73
+ /** @param {*} v */
74
+ const values = v => {
75
+ if (!v || !Array.isArray(v)) { return null;}
76
+ const list = v.map(toValueItem).filter(valueFilter);
77
+ if (!list.length) { return null; }
78
+ return list;
79
+ };
80
+
81
+ /** @import Store from './index.mjs' */
82
+
83
+ /**
84
+ * @template T
85
+ * @param {Store} self
86
+ * @param {(v: any) => any?} toValue
87
+ * @param {T?} [defState]
88
+ * @param {T | ((store: Store, root: Store) => T?) | null} [fn]
89
+ * @returns {[Signal.State<T?>, Signal.Computed<T?>]}
90
+ */
91
+ function createState(self, toValue, defState, fn) {
92
+
93
+ const selfState = new Signal.State(toValue(defState));
94
+ /** @type {Signal.Computed<T>} */
95
+ let scriptState;
96
+ if (typeof fn === 'function') {
97
+ const f = /** @type {(store: Store, root: Store) => T?} */(fn);
98
+ scriptState = new Signal.Computed(() => toValue(f(self, self.root)));
99
+ } else {
100
+ const def = toValue(fn);
101
+ scriptState = new Signal.Computed(() => def);
102
+ }
103
+
104
+ const state = new Signal.Computed(() => {
105
+ const s = selfState.get();
106
+ return s === null ? scriptState.get() : s;
107
+ });
108
+
109
+ return [selfState, state];
110
+
111
+ }
112
+
37
113
  /** @import { Schema } from '../types.mjs' */
38
114
  /**
39
115
  * @template [T=any]
@@ -63,6 +139,15 @@ class Store {
63
139
  * @param {boolean} [options.required]
64
140
  * @param {boolean} [options.readonly]
65
141
  * @param {boolean} [options.disabled]
142
+ *
143
+ * @param {string} [options.label] 字段标签
144
+ * @param {string} [options.description] 字段描述
145
+ * @param {string} [options.placeholder] 占位符
146
+ * @param {number} [options.min] 日期、时间、数字的最小值
147
+ * @param {number} [options.max] 日期、时间、数字的最大值
148
+ * @param {number} [options.step] 日期、时间、数字的步长
149
+ * @param {(Schema.Value.Group | Schema.Value | string | number)[]} [options.values] 可选值
150
+ *
66
151
  * @param {((value: any) => any)?} [options.setValue]
67
152
  * @param {((value: any) => any)?} [options.setState]
68
153
  * @param {((value: any, state: any) => [value: any, state: any])?} [options.convert]
@@ -74,6 +159,7 @@ class Store {
74
159
  setValue, setState, convert, onUpdate, onUpdateState,
75
160
  index, length, new: isNew, parent: parentNode,
76
161
  hidden, clearable, required, disabled, readonly,
162
+ label, description, placeholder, min, max, step, values: values$1
77
163
  }) {
78
164
  this.schema = schema;
79
165
  this.#state.set(typeof state === 'object' && state || {});
@@ -96,13 +182,41 @@ class Store {
96
182
  const creatable = schema.creatable !== false;
97
183
  this.#immutable = immutable;
98
184
  this.#creatable = creatable;
99
- this.#editable = new Signal.Computed(() => newState.get() ? creatable : !immutable);
185
+
186
+ const readonlyFn = schema.readonly;
187
+ const selfReadonly = new Signal.State(typeof readonly === 'boolean' ? readonly : null);
188
+ /** @type {Signal.Computed<boolean>} */
189
+ let readonlyScript;
190
+ if (typeof readonlyFn === 'function') {
191
+ readonlyScript = new Signal.Computed(() => Boolean(readonlyFn(this, this.root)));
192
+ } else {
193
+ const def = Boolean(readonlyFn);
194
+ readonlyScript = new Signal.Computed(() => def);
195
+ }
196
+ const getReadonly = () => {
197
+ if (newState.get() ? !creatable : immutable) { return true; }
198
+ const s = selfReadonly.get();
199
+ return s === null ? readonlyScript.get() : s;
200
+ };
201
+ const readonlyParent = parent ? parent.#readonly : null;
202
+ this.#selfReadonly = selfReadonly;
203
+ this.#readonly = readonlyParent
204
+ ? new Signal.Computed(() => readonlyParent.get() || getReadonly())
205
+ : new Signal.Computed(getReadonly);
100
206
 
101
207
  [this.#selfHidden, this.#hidden] = createBooleanStates(this, hidden, schema.hidden, parent ? parent.#hidden : null);
102
208
  [this.#selfClearable, this.#clearable] = createBooleanStates(this, clearable, schema.clearable, parent ? parent.#clearable : null);
103
209
  [this.#selfRequired, this.#required] = createBooleanStates(this, required, schema.required, parent ? parent.#required : null);
104
210
  [this.#selfDisabled, this.#disabled] = createBooleanStates(this, disabled, schema.disabled, parent ? parent.#disabled : null);
105
- [this.#selfReadonly, this.#readonly] = createBooleanStates(this, readonly, schema.readonly, parent ? parent.#readonly : null);
211
+
212
+ [this.#selfLabel, this.#label] = createState(this, string, label, schema.label);
213
+ [this.#selfDescription, this.#description] = createState(this, string, description, schema.description);
214
+ [this.#selfPlaceholder, this.#placeholder] = createState(this, string, placeholder, schema.placeholder);
215
+ [this.#selfMin, this.#min] = createState(this, number, min, schema.min);
216
+ [this.#selfMax, this.#max] = createState(this, number, max, schema.max);
217
+ [this.#selfStep, this.#step] = createState(this, number, step, schema.step);
218
+ // @ts-ignore
219
+ [this.#selfValues, this.#values] = createState(this, values, values$1, schema.values);
106
220
 
107
221
  if (isNull) {
108
222
  this.#null = true;
@@ -156,13 +270,10 @@ class Store {
156
270
  #new
157
271
  /** @readonly @type {Signal.State<boolean>} */
158
272
  #selfNew
159
- /** @readonly @type {Signal.Computed<boolean>} */
160
- #editable
161
273
  get selfNew() { return this.#selfNew.get(); }
162
274
  set selfNew(v) { this.#selfNew.set(Boolean(v)); }
163
275
  get new() { return this.#new.get(); }
164
276
  set new(v) { this.#selfNew.set(Boolean(v)); }
165
- get editable() { return this.#editable.get(); }
166
277
 
167
278
  /** @readonly @type {Signal.State<boolean?>} */
168
279
  #selfHidden
@@ -212,6 +323,77 @@ class Store {
212
323
 
213
324
 
214
325
 
326
+ /** @readonly @type {Signal.State<string?>} */
327
+ #selfLabel
328
+ /** @readonly @type {Signal.Computed<string?>} */
329
+ #label
330
+ get selfLabel() { return this.#selfLabel.get(); }
331
+ set selfLabel(v) { this.#selfLabel.set(string(v)); }
332
+ get label() { return this.#label.get(); }
333
+ set label(v) { this.#selfLabel.set(string(v)); }
334
+
335
+
336
+ /** @readonly @type {Signal.State<string?>} */
337
+ #selfDescription
338
+ /** @readonly @type {Signal.Computed<string?>} */
339
+ #description
340
+ get selfDescription() { return this.#selfDescription.get(); }
341
+ set selfDescription(v) { this.#selfDescription.set(string(v)); }
342
+ get description() { return this.#description.get(); }
343
+ set description(v) { this.#selfDescription.set(string(v)); }
344
+
345
+ /** @readonly @type {Signal.State<string?>} */
346
+ #selfPlaceholder
347
+ /** @readonly @type {Signal.Computed<string?>} */
348
+ #placeholder
349
+ get selfPlaceholder() { return this.#selfPlaceholder.get(); }
350
+ set selfPlaceholder(v) { this.#selfPlaceholder.set(string(v)); }
351
+ get placeholder() { return this.#placeholder.get(); }
352
+ set placeholder(v) { this.#selfPlaceholder.set(string(v)); }
353
+
354
+
355
+ /** @readonly @type {Signal.State<number?>} */
356
+ #selfMin
357
+ /** @readonly @type {Signal.Computed<number?>} */
358
+ #min
359
+ get selfMin() { return this.#selfMin.get(); }
360
+ set selfMin(v) { this.#selfMin.set(number(v)); }
361
+ get min() { return this.#min.get(); }
362
+ set min(v) { this.#selfMin.set(number(v)); }
363
+
364
+
365
+ /** @readonly @type {Signal.State<number?>} */
366
+ #selfMax
367
+ /** @readonly @type {Signal.Computed<number?>} */
368
+ #max
369
+ get selfMax() { return this.#selfMax.get(); }
370
+ set selfMax(v) { this.#selfMax.set(number(v)); }
371
+ get max() { return this.#max.get(); }
372
+ set max(v) { this.#selfMax.set(number(v)); }
373
+
374
+
375
+ /** @readonly @type {Signal.State<number?>} */
376
+ #selfStep
377
+ /** @readonly @type {Signal.Computed<number?>} */
378
+ #step
379
+ get selfStep() { return this.#selfStep.get(); }
380
+ set selfStep(v) { this.#selfStep.set(number(v)); }
381
+ get step() { return this.#step.get(); }
382
+ set step(v) { this.#selfStep.set(number(v)); }
383
+
384
+
385
+ /** @readonly @type {Signal.State<(Schema.Value.Group | Schema.Value)[] | null>} */
386
+ #selfValues
387
+ /** @readonly @type {Signal.Computed<(Schema.Value.Group | Schema.Value)[] | null>} */
388
+ #values
389
+ get selfValues() { return this.#selfValues.get(); }
390
+ set selfValues(v) { this.#selfValues.set(values(v)); }
391
+ get values() { return this.#values.get(); }
392
+ set values(v) { this.#selfValues.set(values(v)); }
393
+
394
+
395
+
396
+
215
397
 
216
398
  /** @returns {IterableIterator<[key: string | number, value: Store]>} */
217
399
  *[Symbol.iterator]() {}
@@ -718,7 +900,7 @@ class ParseError extends Error {
718
900
 
719
901
  /** @import * as Layout from './index.mjs' */
720
902
 
721
- const attrPattern = /^(?<decorator>[:@!+*\.]|style:|样式:)?(?<name>-?[\w\p{Unified_Ideograph}_][-\w\p{Unified_Ideograph}_:\d\.]*)$/u;
903
+ const attrPattern = /^(?<decorator>[:@!+*\.?]|style:|样式:)?(?<name>-?[\w\p{Unified_Ideograph}_][-\w\p{Unified_Ideograph}_:\d\.]*)$/u;
722
904
  const nameRegex = /^(?<name>[a-zA-Z$\p{Unified_Ideograph}_][\da-zA-Z$\p{Unified_Ideograph}_]*)?$/u;
723
905
  /**
724
906
  * @param {Layout.Node} node
@@ -726,7 +908,7 @@ const nameRegex = /^(?<name>[a-zA-Z$\p{Unified_Ideograph}_][\da-zA-Z$\p{Unified_
726
908
  * @param {Exclude<Layout.Options['creteEvent'], undefined>} creteEvent
727
909
  */
728
910
  function createAttributeAdder(node, creteCalc, creteEvent) {
729
- const { attrs, directives, events, classes, styles, vars, aliases } = node;
911
+ const { attrs, directives, events, classes, styles, vars, aliases, params } = node;
730
912
  /**
731
913
  * @param {string} qName
732
914
  * @param {string} value
@@ -756,20 +938,23 @@ function createAttributeAdder(node, creteCalc, creteEvent) {
756
938
  } else if (decorator === '+') {
757
939
  vars[name] = !value ? '' : nameRegex.test(value) ? value : creteCalc(value);
758
940
  } else if (decorator === '*') {
759
- aliases[name] = nameRegex.test(value) ? value : creteEvent(value);
941
+ aliases[name] = nameRegex.test(value) ? value : creteCalc(value);
942
+ } else if (decorator === '?') {
943
+ params[name] = nameRegex.test(value) ? value : creteCalc(value);
760
944
  } else if (decorator === '!') {
761
945
  const key = name.toString();
762
946
  switch (key) {
763
- case 'fragment':
764
- case 'else':
947
+ case 'fragment': directives.fragment = value || true; break;
948
+ case 'else': directives.else = true; break;
765
949
  case 'enum':
766
- directives[key] = true;
950
+ directives.enum = value ? nameRegex.test(value) ? value : creteCalc(value) : true;
767
951
  break;
768
952
  case 'if':
769
953
  case 'text':
770
954
  case 'html':
771
955
  directives[key] = nameRegex.test(value) ? value : creteCalc(value);
772
956
  break;
957
+ case 'template':
773
958
  case 'bind':
774
959
  case 'value':
775
960
  case 'comment':
@@ -801,6 +986,7 @@ function createElement(name, is) {
801
986
  styles: Object.create(null),
802
987
  vars: Object.create(null),
803
988
  aliases: Object.create(null),
989
+ params: Object.create(null),
804
990
  };
805
991
  }
806
992
 
@@ -1447,13 +1633,15 @@ function toString(value, formable) {
1447
1633
  /**
1448
1634
  * @typedef {object} Directives
1449
1635
  *
1450
- * @property {boolean} [fragment]
1636
+ * @property {string} [template]
1637
+ *
1638
+ * @property {boolean | string} [fragment]
1451
1639
  *
1452
1640
  * @property {string | Function} [if]
1453
1641
  * @property {boolean} [else]
1454
1642
  *
1455
1643
  * @property {string} [value] 值关联(关联为列表)
1456
- * @property {boolean} [enum] 列表属性枚举
1644
+ * @property {boolean | string | Function} [enum] 列表属性枚举
1457
1645
  *
1458
1646
  * @property {string} [bind]
1459
1647
  * @property {string | Function} [text]
@@ -1476,6 +1664,7 @@ function toString(value, formable) {
1476
1664
  * @property {string?} [is]
1477
1665
  * @property {string} [id]
1478
1666
  * @property {Record<string, string | {name: string} | ((global: any) => void)>} attrs
1667
+ * @property {Record<string, string | ((global: any) => void)>} params
1479
1668
  * @property {Record<string, string | boolean | ((global: any) => void)>} classes
1480
1669
  * @property {Record<string, string | ((global: any) => void)>} styles
1481
1670
  * @property {Record<string, string | (($event: any, global: any) => void)>} events
@@ -1536,6 +1725,28 @@ function watch(getter, callback) {
1536
1725
  return () => { w.unwatch(computed); };
1537
1726
  }
1538
1727
 
1728
+ /** @import * as Layout from '../Layout/index.mjs' */
1729
+
1730
+ const bindable = {
1731
+ new: true,
1732
+ readonly: true,
1733
+
1734
+ required: true,
1735
+ clearable: true,
1736
+ hidden: true,
1737
+ disabled: true,
1738
+
1739
+ label: true,
1740
+ description: true,
1741
+ placeholder: true,
1742
+ min: true,
1743
+ max: true,
1744
+ step: true,
1745
+ values: true,
1746
+ };
1747
+ /** @type {Set<keyof typeof bindable>} */
1748
+ // @ts-ignore
1749
+ const bindableSet = new Set(Object.keys(bindable));
1539
1750
  /** @typedef {{get(): any; set?(v: any): void; exec?: null; store?: Store; calc?: null; }} ValueDefine */
1540
1751
  /** @typedef {{get?: null; exec(...p: any[]): any; calc?: null}} ExecDefine */
1541
1752
  /** @typedef {{get?: null; calc(...p: any[]): any; exec?: null;}} CalcDefine */
@@ -1555,13 +1766,12 @@ function *toItem(val, key = '', sign = '$') {
1555
1766
  yield [`${key}${sign}length`, {get: () => val.length}];
1556
1767
  yield [`${key}${sign}creatable`, {get: () => val.creatable}];
1557
1768
  yield [`${key}${sign}immutable`, {get: () => val.immutable}];
1558
- yield [`${key}${sign}new`, {get: () => val.new}];
1559
- yield [`${key}${sign}editable`, {get: () => val.editable}];
1560
- yield [`${key}${sign}hidden`, {get: () => val.hidden}];
1561
- yield [`${key}${sign}clearable`, {get: () => val.clearable}];
1562
- yield [`${key}${sign}required`, {get: () => val.required}];
1563
- yield [`${key}${sign}disabled`, {get: () => val.disabled}];
1564
- yield [`${key}${sign}readonly`, {get: () => val.readonly}];
1769
+
1770
+ yield [`${key}${sign}schema`, {get: () => val.schema}];
1771
+
1772
+ for (const k of bindableSet) {
1773
+ yield [`${key}${sign}${k}`, {get: () => val[k]}];
1774
+ }
1565
1775
  if (!(val instanceof ArrayStore)) { return; }
1566
1776
  yield [`${key}${sign}insert`, {exec: (index, value) => val.insert(index, value)}];
1567
1777
  yield [`${key}${sign}add`, {exec: (v) => val.add(v)}];
@@ -1628,6 +1838,21 @@ class Environment {
1628
1838
  */
1629
1839
  watch(value, cb) { return watch(() => this.exec(value), cb); }
1630
1840
 
1841
+ /**
1842
+ * @param {string | Function} name
1843
+ */
1844
+ enum(name) {
1845
+ if (typeof name === 'function') {
1846
+ return () => name(this.getters);
1847
+ }
1848
+ if (typeof name !== 'string') { return null; }
1849
+ const item = this.#items[name];
1850
+ if (typeof item?.get !== 'function') { return null }
1851
+ const store = item.store;
1852
+ return store instanceof Store ? store : item.get;
1853
+
1854
+ }
1855
+
1631
1856
  /**
1632
1857
  * @param {string} name
1633
1858
  * @param {string} type
@@ -1641,11 +1866,11 @@ class Environment {
1641
1866
  switch(type) {
1642
1867
  case 'value': return watch(() => store.value, cb);
1643
1868
  case 'state': return watch(() => store.state, cb);
1644
- case 'required': return watch(() => store.required, cb);
1645
- case 'clearable': return watch(() => store.clearable, cb);
1646
- case 'hidden': return watch(() => store.hidden, cb);
1647
- case 'disabled': return watch(() => store.disabled, cb);
1648
- case 'readonly': return watch(() => store.readonly || !store.editable, cb);
1869
+ }
1870
+ // @ts-ignore
1871
+ if (bindableSet.has(type)) {
1872
+ // @ts-ignore
1873
+ return watch(() => store[type], cb);
1649
1874
  }
1650
1875
  }
1651
1876
  /**
@@ -1661,15 +1886,13 @@ class Environment {
1661
1886
  if (typeof get !== 'function') { return; }
1662
1887
  return { '$value': cb => watch(get, cb) }
1663
1888
  }
1664
- return {
1665
- '$value': cb => watch(() => store.value, cb),
1666
- '$state': cb => watch(() => store.state, cb),
1667
- '$required': cb => watch(() => store.required, cb),
1668
- '$clearable': cb => watch(() => store.clearable, cb),
1669
- '$hidden': cb => watch(() => store.hidden, cb),
1670
- '$disabled': cb => watch(() => store.disabled, cb),
1671
- '$readonly': cb => watch(() => store.readonly || !store.editable, cb),
1672
- }
1889
+ /** @type {Record<string, ((cb: (value: any) => void) => () => void) | void> | void} */
1890
+ const res = Object.fromEntries([...bindableSet].map(v => [
1891
+ `$${v}`, cb => watch(() => store[v], cb)
1892
+ ]));
1893
+ res.$value = cb => watch(() => store.value, cb);
1894
+ res.$state = cb => watch(() => store.state, cb);
1895
+ return res;
1673
1896
  }
1674
1897
  /**
1675
1898
  * @param {string} name
@@ -1772,6 +1995,8 @@ class Environment {
1772
1995
  #explicit = Object.create(null);
1773
1996
  /** @type {Store?} */
1774
1997
  #store = null
1998
+ /** @type {Record<string, any>?} */
1999
+ #object = null
1775
2000
  /** @type {Store?} */
1776
2001
  #parent = null
1777
2002
  /** @type {Record<string, ValueDefine | ExecDefine | CalcDefine>?} */
@@ -1787,6 +2012,7 @@ class Environment {
1787
2012
  }));
1788
2013
  const store = this.#store;
1789
2014
  const parent = this.#parent;
2015
+ const object = this.#object;
1790
2016
  if (store) {
1791
2017
  for (const [key, item] of toItem(store)) {
1792
2018
  ais[key] = item;
@@ -1795,6 +2021,11 @@ class Environment {
1795
2021
  ais[key] = item;
1796
2022
  }
1797
2023
  }
2024
+ if (object) {
2025
+ for (const k of Object.keys(object)) {
2026
+ ais[`$${k}`] = {get: () => object[k]};
2027
+ }
2028
+ }
1798
2029
  this.#allItems = ais;
1799
2030
  return ais;
1800
2031
  }
@@ -1803,7 +2034,7 @@ class Environment {
1803
2034
  * @param {Store} store
1804
2035
  * @param {Store} [parent]
1805
2036
  */
1806
- setValue(store, parent) {
2037
+ setStore(store, parent) {
1807
2038
  const cloned = new Environment(this);
1808
2039
  cloned.#store = store;
1809
2040
  if (parent) { cloned.#parent = parent; }
@@ -1819,6 +2050,73 @@ class Environment {
1819
2050
  }
1820
2051
  return cloned;
1821
2052
  }
2053
+ /**
2054
+ *
2055
+ * @param {Layout.Node} template
2056
+ * @param {Layout.Node} source
2057
+ * @param {Environment} sourceEnv
2058
+ */
2059
+ params({params}, {attrs}, sourceEnv) {
2060
+ if (Object.keys(params).length === 0) { return this; }
2061
+ const cloned = new Environment(this);
2062
+ cloned.#store = this.#store;
2063
+ cloned.#parent = this.#parent;
2064
+ cloned.#object = this.#object;
2065
+ const explicit = cloned.#explicit;
2066
+ const items = cloned.#items;
2067
+ for (const [key, param] of Object.entries(params)) {
2068
+ const attr = key in attrs ? attrs[key] : null;
2069
+ if (typeof attr === 'string') {
2070
+ explicit[key] = items[key] = {get: () => attr};
2071
+ } else if (attr && typeof attr === 'object') {
2072
+ const item = sourceEnv.#items[attr.name];
2073
+ if (!item?.get) { continue; }
2074
+ if (!item.store) {
2075
+ explicit[key] = items[key] = item;
2076
+ continue;
2077
+ }
2078
+ for (const [k, it] of toItem(item.store, key)) {
2079
+ explicit[k] = items[k] = it;
2080
+ }
2081
+ continue;
2082
+
2083
+ } else if (typeof attr === 'function') {
2084
+ const val = new Signal.Computed(() => attr(sourceEnv.getters));
2085
+ explicit[key] = items[key] = {
2086
+ get: () => { return val.get(); },
2087
+ };
2088
+
2089
+ continue;
2090
+ } else if (typeof param === 'function') {
2091
+ const getters = cloned.getters;
2092
+ cloned.#getters = null;
2093
+ const val = new Signal.Computed(() => param(getters));
2094
+ explicit[key] = items[key] = {
2095
+ get: () => { return val.get(); },
2096
+ };
2097
+ continue;
2098
+
2099
+ } else {
2100
+ const item = items[param];
2101
+ if (!item?.get) { continue; }
2102
+ if (!item.store) {
2103
+ explicit[key] = items[key] = item;
2104
+ continue;
2105
+ }
2106
+ for (const [k, it] of toItem(item.store, key)) {
2107
+ explicit[k] = items[k] = it;
2108
+ }
2109
+ continue;
2110
+ }
2111
+ }
2112
+ return cloned;
2113
+ }
2114
+ /** @param {Record<string, any>} object */
2115
+ setObject(object) {
2116
+ const cloned = new Environment(this);
2117
+ cloned.#object = object;
2118
+ return cloned;
2119
+ }
1822
2120
  /**
1823
2121
  *
1824
2122
  * @param {Record<string, string | Function>} aliases
@@ -1829,6 +2127,7 @@ class Environment {
1829
2127
  const cloned = new Environment(this);
1830
2128
  cloned.#store = this.#store;
1831
2129
  cloned.#parent = this.#parent;
2130
+ cloned.#object = this.#object;
1832
2131
  const explicit = cloned.#explicit;
1833
2132
  const items = cloned.#items;
1834
2133
  for (const [key, name] of Object.entries(aliases)) {
@@ -2556,6 +2855,14 @@ function getAttrs(el, attr) {
2556
2855
  const tagBindMap = {
2557
2856
  input: {
2558
2857
  attrs: {
2858
+ /** @param {any} v @param {HTMLInputElement} e */
2859
+ $min: (v, e) => {e.min = v;},
2860
+ /** @param {any} v @param {HTMLInputElement} e */
2861
+ $max: (v, e) => {e.max = v;},
2862
+ /** @param {any} v @param {HTMLInputElement} e */
2863
+ $step: (v, e) => {e.step = v;},
2864
+ /** @param {any} v @param {HTMLInputElement} e */
2865
+ $placeholder: (v, e) => {e.placeholder = v;},
2559
2866
  /** @param {any} v @param {HTMLInputElement} e */
2560
2867
  $disabled: (v, e) => {e.disabled = v;},
2561
2868
  /** @param {any} v @param {HTMLInputElement} e */
@@ -2588,13 +2895,15 @@ const tagBindMap = {
2588
2895
  },
2589
2896
  textarea: {
2590
2897
  attrs: {
2591
- /** @param {any} v @param {HTMLInputElement} e */
2898
+ /** @param {any} v @param {HTMLTextAreaElement} e */
2899
+ $placeholder: (v, e) => {e.placeholder = v;},
2900
+ /** @param {any} v @param {HTMLTextAreaElement} e */
2592
2901
  $disabled: (v, e) => {e.disabled = v;},
2593
- /** @param {any} v @param {HTMLInputElement} e */
2902
+ /** @param {any} v @param {HTMLTextAreaElement} e */
2594
2903
  $readonly: (v, e) => {e.readOnly = v;},
2595
- /** @param {any} v @param {HTMLInputElement} e */
2904
+ /** @param {any} v @param {HTMLTextAreaElement} e */
2596
2905
  $required: (v, e) => {e.required = v;},
2597
- /** @param {any} v @param {HTMLInputElement} e */
2906
+ /** @param {any} v @param {HTMLTextAreaElement} e */
2598
2907
  $value: (v, e) => { e.value = toText$1(v); },
2599
2908
  },
2600
2909
  events: {
@@ -2603,13 +2912,11 @@ const tagBindMap = {
2603
2912
  },
2604
2913
  select: {
2605
2914
  attrs: {
2606
- /** @param {any} v @param {HTMLInputElement} e */
2915
+ /** @param {any} v @param {HTMLSelectElement} e */
2607
2916
  $disabled: (v, e) => {e.disabled = v;},
2608
- /** @param {any} v @param {HTMLInputElement} e */
2609
- $readonly: (v, e) => {e.readOnly = v;},
2610
- /** @param {any} v @param {HTMLInputElement} e */
2917
+ /** @param {any} v @param {HTMLSelectElement} e */
2611
2918
  $required: (v, e) => {e.required = v;},
2612
- /** @param {any} v @param {HTMLInputElement} e */
2919
+ /** @param {any} v @param {HTMLSelectElement} e */
2613
2920
  $value: (v, e) => { e.value = toText$1(v); },
2614
2921
  },
2615
2922
  events: {
@@ -2760,7 +3067,7 @@ function renderArray(layout, parent, next, store, env, renderItem) {
2760
3067
  if (!old) {
2761
3068
  const ItemStart = parent.insertBefore(document.createComment(''), nextNode);
2762
3069
  const itemEnd = parent.insertBefore(document.createComment(''), nextNode);
2763
- const d = renderItem(layout, parent, itemEnd, child, env.setValue(child, store));
3070
+ const d = renderItem(layout, parent, itemEnd, child, env.setStore(child, store));
2764
3071
  seMap.set(child, [ItemStart, itemEnd, d]);
2765
3072
  continue;
2766
3073
  }
@@ -2784,6 +3091,7 @@ function renderArray(layout, parent, next, store, env, renderItem) {
2784
3091
 
2785
3092
  return () => {
2786
3093
  start.remove();
3094
+ destroyMap(seMap);
2787
3095
  childrenResult();
2788
3096
  };
2789
3097
  }
@@ -2852,15 +3160,24 @@ function renderFillDirectives(parent, next, envs, { text, html }) {
2852
3160
  * @param {Element} parent
2853
3161
  * @param {Node?} next
2854
3162
  * @param {Environment} envs
2855
- * @param {(layout: Layout.Node) => () => void} renderItem
3163
+ * @param {Record<string, [Layout.Node, Environment]>} templates
3164
+ * @param {(layout: Layout.Node, templates: Record<string, any>) => () => void} renderItem
2856
3165
  * @returns {() => void}
2857
3166
  */
2858
- function renderList(layouts, parent, next, envs, renderItem) {
3167
+ function renderList(layouts, parent, next, envs, templates, renderItem) {
2859
3168
 
2860
3169
  /** @type {Set<() => void>?} */
2861
3170
  let bkList = new Set();
2862
3171
  /** @type {[string | Function | null, Layout.Node][]} */
2863
3172
  let ifList = [];
3173
+ /** @type {Record<string, [Layout.Node, Environment]>} */
3174
+ let currentTemplates = Object.create(templates);
3175
+ for (const layout of layouts) {
3176
+ if (typeof layout === 'string') { continue; }
3177
+ const name = layout.directives.template;
3178
+ if (!name) { continue; }
3179
+ currentTemplates[name] = [layout, envs];
3180
+ }
2864
3181
 
2865
3182
  /** @param {[string | Function | null, Layout.Node][]} list */
2866
3183
  function renderIf(list) {
@@ -2878,7 +3195,7 @@ function renderList(layouts, parent, next, envs, renderItem) {
2878
3195
  function renderIndex(index) {
2879
3196
  const layout = list[index]?.[1];
2880
3197
  if (!layout) { return; }
2881
- destroy = renderItem(layout);
3198
+ destroy = renderItem(layout, currentTemplates);
2882
3199
  }
2883
3200
  bkList.add(() => {
2884
3201
  destroy();
@@ -2905,6 +3222,11 @@ function renderList(layouts, parent, next, envs, renderItem) {
2905
3222
  bkList.add(() => node.remove());
2906
3223
  continue;
2907
3224
  }
3225
+ if (layout.directives.template) {
3226
+ renderIf(ifList);
3227
+ ifList = [];
3228
+ continue;
3229
+ }
2908
3230
  if (ifList.length && layout.directives.else) {
2909
3231
  const ifv = layout.directives.if || null;
2910
3232
  ifList.push([ifv, layout]);
@@ -2922,7 +3244,7 @@ function renderList(layouts, parent, next, envs, renderItem) {
2922
3244
  continue;
2923
3245
  }
2924
3246
  bkList.add(
2925
- renderItem(layout)
3247
+ renderItem(layout, currentTemplates)
2926
3248
  );
2927
3249
  }
2928
3250
 
@@ -2936,36 +3258,147 @@ function renderList(layouts, parent, next, envs, renderItem) {
2936
3258
  };
2937
3259
  }
2938
3260
 
3261
+ /** @import * as Layout from '../Layout/index.mjs' */
3262
+ /** @import Store, { ObjectStore } from '../Store/index.mjs' */
3263
+
3264
+ /**
3265
+ *
3266
+ * @param {Layout.Node} layout
3267
+ * @param {Element} parent
3268
+ * @param {Node?} next
3269
+ * @param {ObjectStore} store
3270
+ * @param {Environment} env
3271
+ * @param {(layout: Layout.Node, parent: Element, next: Node | null, store: Store, env: any) => () => void} renderItem
3272
+ */
3273
+ function renderObject(layout, parent, next, store, env, renderItem) {
3274
+ /** @type {(() => void)[]} */
3275
+ const children = [];
3276
+ for (const [k, child] of [...store]) {
3277
+ children.push(renderItem(layout, parent, next, child, env.setStore(child, store)));
3278
+ }
3279
+
3280
+ return () => {
3281
+ for (const d of children) {
3282
+ d();
3283
+ }
3284
+ };
3285
+ }
3286
+
3287
+ /** @import * as Layout from '../Layout/index.mjs' */
2939
3288
  /** @import Store from '../Store/index.mjs' */
2940
3289
 
2941
3290
  /**
3291
+ *
2942
3292
  * @param {Layout.Node} layout
2943
3293
  * @param {Element} parent
2944
3294
  * @param {Node?} next
3295
+ * @param {Store} store
3296
+ * @param {() => any} getter
2945
3297
  * @param {Environment} env
2946
- * @param {(layout: Layout.Node) => () => void} renderItem
2947
- * @returns {() => void}
3298
+ * @param {(layout: Layout.Node, parent: Element, next: Node | null, store: Store, env: any) => () => void} renderItem
2948
3299
  */
2949
- function renderFragment(layout, parent, next, env, renderItem) {
2950
- return renderFillDirectives(parent, next, env, layout.directives) ||
2951
- renderList(layout.children || [], parent, next, env, renderItem);
3300
+ function renderEnum(layout, parent, next, store, getter, env, renderItem) {
3301
+
3302
+ /** @type {Signal.Computed<[value: any, index: number, kKey: any][]>} */
3303
+ const list = new Signal.Computed(() => {
3304
+ const values = getter();
3305
+ if (typeof values === 'number') {
3306
+ const n = Math.floor(values);
3307
+ if (!n) { return []; }
3308
+ return Array(n).fill(0).map((_, i) => [i + 1, i, i]);
3309
+ }
3310
+ if (!values || typeof values !== 'object') { return []; }
3311
+ if (Array.isArray(values)) {
3312
+ return values.map((v, i) => [v, i, i]);
3313
+ }
3314
+ // TODO: 转列表
3315
+ return Object.entries(values).map(([k,v], i) => [v, i, k]);
3316
+ });
3317
+ const start = parent.insertBefore(document.createComment(''), next);
3318
+ /** @type {[Comment, Comment, () => void, key: any, Signal.State<any>, Signal.State<any>][]} */
3319
+ let seMap = [];
3320
+ /** @param {[Comment, Comment, () => void, key: any, Signal.State<any>, Signal.State<any>][]} map */
3321
+ function destroyMap(map) {
3322
+ for (const [s, e, d] of map) {
3323
+ d();
3324
+ s.remove();
3325
+ e.remove();
3326
+ }
3327
+ }
3328
+ const childrenResult = watch(() => list.get(), function render(children) {
3329
+ if (!start.parentNode) { return; }
3330
+ let nextNode = start.nextSibling;
3331
+ const oldSeMap = seMap;
3332
+ seMap = [];
3333
+ for (const [value, index, key] of children) {
3334
+ const index2 = oldSeMap.findIndex((v) => v[3] === key);
3335
+ const [old] = index2 >= 0 ? oldSeMap.splice(index2, 1) : [];
3336
+ if (!old) {
3337
+ const ItemStart = parent.insertBefore(document.createComment(''), nextNode);
3338
+ const itemEnd = parent.insertBefore(document.createComment(''), nextNode);
3339
+ const valueState = new Signal.State(value);
3340
+ const indexState = new Signal.State(index);
3341
+ const d = renderItem(layout, parent, itemEnd, store, env.setObject({
3342
+ get key() { return key; },
3343
+ get value() { return valueState.get(); },
3344
+ get index() { return indexState.get(); },
3345
+ }));
3346
+ seMap.push([ItemStart, itemEnd, d, key, valueState, indexState]);
3347
+ continue;
3348
+ }
3349
+ seMap.push(old);
3350
+ old[4].set(value);
3351
+ old[5].set(index);
3352
+ if (nextNode === old[0]) {
3353
+ nextNode = old[1].nextSibling;
3354
+ continue;
3355
+ }
3356
+ /** @type {Node?} */
3357
+ let c = old[0];
3358
+ while (c && c !== old[1]) {
3359
+ const o = c;
3360
+ c = c.nextSibling;
3361
+ parent.insertBefore(o, nextNode);
3362
+ }
3363
+ parent.insertBefore(old[1], nextNode);
3364
+ }
3365
+ destroyMap(oldSeMap);
3366
+ });
2952
3367
 
3368
+ return () => {
3369
+ start.remove();
3370
+ destroyMap(seMap);
3371
+ childrenResult();
3372
+ };
2953
3373
  }
3374
+
3375
+ /** @import Store from '../Store/index.mjs' */
3376
+
2954
3377
  /**
2955
3378
  * @param {Layout.Node} layout
2956
3379
  * @param {Element} parent
2957
3380
  * @param {Node?} next
2958
3381
  * @param {Store} store
2959
3382
  * @param {Environment} env
3383
+ * @param {Record<string, [Layout.Node, Environment]>} templates
2960
3384
  * @param {string[]} componentPath
2961
3385
  * @param {((path: string[]) => Component?)?} [getComponent]
2962
3386
  */
2963
- function renderItem(layout, parent, next, store, env, componentPath, getComponent) {
3387
+ function renderItem(layout, parent, next, store, env, templates, componentPath, getComponent) {
2964
3388
  env = env.set(layout.aliases, layout.vars);
3389
+ const fragment = layout.directives.fragment;
3390
+ if (fragment && typeof fragment === 'string') {
3391
+ const template = templates[fragment];
3392
+ if (!template) { return () => {}; }
3393
+ const [templateLayout, templateEnv] = template;
3394
+ const newEnv = templateEnv.params(templateLayout, layout, env);
3395
+ return render(templateLayout, parent, next, store, newEnv, templates, componentPath, getComponent);
3396
+ }
2965
3397
  if (!layout.name || layout.directives.fragment) {
2966
- return renderFragment(layout, parent, next, env, l => {
2967
- return render(l, parent, next, store, env, componentPath, getComponent);
2968
- });
3398
+ return renderFillDirectives(parent, next, env, layout.directives) ||
3399
+ renderList(layout.children || [], parent, next, env, templates, (layout, templates) => {
3400
+ return render(layout, parent, next, store, env, templates, componentPath, getComponent);
3401
+ });
2969
3402
  }
2970
3403
  const path = [...componentPath, layout.name];
2971
3404
  const component = getComponent?.(path);
@@ -2989,13 +3422,13 @@ function renderItem(layout, parent, next, store, env, componentPath, getComponen
2989
3422
  : createTagComponent(context, component.tag, component.is)
2990
3423
  : createTagComponent(context, layout.name, layout.is);
2991
3424
  const root = Array.isArray(r) ? r[0] : r;
2992
- const slot = Array.isArray(r) && r[1] || root;
3425
+ const slot = Array.isArray(r) ? r[1] : root;
2993
3426
  parent.insertBefore(root, next);
2994
- const children =
3427
+ const children = slot ?
2995
3428
  renderFillDirectives(slot, null, env, layout.directives)
2996
- || renderList(layout.children || [], slot, null, env, l => {
2997
- return render(l, slot, null, store, env, componentPath, getComponent);
2998
- });
3429
+ || renderList(layout.children || [], slot, null, env, templates, (layout, templates) => {
3430
+ return render(layout, slot, null, store, env, templates, componentPath, getComponent);
3431
+ }) : () => {};
2999
3432
 
3000
3433
 
3001
3434
  bindClasses(root, layout.classes, env);
@@ -3017,26 +3450,41 @@ function renderItem(layout, parent, next, store, env, componentPath, getComponen
3017
3450
  * @param {Node?} next
3018
3451
  * @param {Store} store
3019
3452
  * @param {Environment} env
3453
+ * @param {Record<string, [Layout.Node, Environment]>} templates
3020
3454
  * @param {string[]} componentPath
3021
3455
  * @param {((path: string[]) => Component?)?} [getComponent]
3022
3456
  * @returns {() => void}
3023
3457
  */
3024
- function render(layout, parent, next, store, env, componentPath, getComponent) {
3458
+ function render(layout, parent, next, store, env, templates, componentPath, getComponent) {
3025
3459
  const { directives } = layout;
3026
3460
  const { value } = directives;
3027
3461
  if (value) {
3028
3462
  const newStore = store.child(value);
3029
3463
  if (!newStore) { return () => {}; }
3030
3464
  store = newStore;
3031
- env = env.setValue(store);
3465
+ env = env.setStore(store);
3032
3466
  }
3033
- if (!directives.enum) {
3034
- return renderItem(layout, parent, next, store, env, componentPath, getComponent);
3467
+ const enumValue = directives.enum;
3468
+ if (!enumValue) {
3469
+ return renderItem(layout, parent, next, store, env, templates, componentPath, getComponent);
3035
3470
  }
3036
- if (!(store instanceof ArrayStore)) { return () => { }; }
3037
- return renderArray(layout, parent, next, store, env, (a, b, c, store, env) => {
3038
- return renderItem(a, b, c, store, env, componentPath, getComponent);
3039
- });
3471
+ const newStore = enumValue === true ? store : env.enum(enumValue);
3472
+ if (newStore instanceof ArrayStore) {
3473
+ return renderArray(layout, parent, next, newStore, env, (a, b, c, store, env) => {
3474
+ return renderItem(a, b, c, store, env, templates, componentPath, getComponent);
3475
+ });
3476
+ }
3477
+ if (newStore instanceof ObjectStore) {
3478
+ return renderObject(layout, parent, next, newStore, env, (a, b, c, store, env) => {
3479
+ return renderItem(a, b, c, store, env, templates, componentPath, getComponent);
3480
+ });
3481
+ }
3482
+ if (typeof newStore === 'function') {
3483
+ return renderEnum(layout, parent, next, store, newStore, env, (a, b, c, store, env) => {
3484
+ return renderItem(a, b, c, store, env, templates, componentPath, getComponent);
3485
+ });
3486
+ }
3487
+ return () => { };
3040
3488
  }
3041
3489
 
3042
3490
  /**
@@ -3067,9 +3515,10 @@ function index (store, layouts, parent, opt1, opt2) {
3067
3515
  const options = [opt1, opt2];
3068
3516
  const components = options.find(v => typeof v === 'function');
3069
3517
  const global = options.find(v => typeof v === 'object');
3070
- const env = new Environment(global).setValue(store);
3071
- return renderList(layouts, parent, null, env, l => {
3072
- return render(l, parent, null, store, env, [], components);
3518
+ const env = new Environment(global).setStore(store);
3519
+ const templates = Object.create(null);
3520
+ return renderList(layouts, parent, null, env, templates, (layout, templates) => {
3521
+ return render(layout, parent, null, store, env, templates, [], components);
3073
3522
  });
3074
3523
  }
3075
3524