@neeloong/form 0.8.0 → 0.9.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.8.0
2
+ * @neeloong/form v0.9.0
3
3
  * (c) 2024-2025 Fierflame
4
4
  * @license Apache-2.0
5
5
  */
@@ -73,7 +73,7 @@ function toValueItem(v) {
73
73
 
74
74
  }
75
75
  /** @param {*} v */
76
- const values = v => {
76
+ const values$1 = v => {
77
77
  if (!v || !Array.isArray(v)) { return null;}
78
78
  const list = v.map(toValueItem).filter(valueFilter);
79
79
  if (!list.length) { return null; }
@@ -387,7 +387,7 @@ class Store {
387
387
  validator, validators,
388
388
  index, length, new: isNew, parent: parentNode,
389
389
  hidden, clearable, required, disabled, readonly,
390
- label, description, placeholder, min, max, step, minLength, maxLength, pattern, values: values$1
390
+ label, description, placeholder, min, max, step, minLength, maxLength, pattern, values
391
391
  } = {}) {
392
392
  this.schema = schema;
393
393
  this.#state.set(typeof state === 'object' && state || {});
@@ -450,7 +450,7 @@ class Store {
450
450
  [this.#selfMaxLength, this.#maxLength] = createState(this, number, maxLength, schema.maxLength);
451
451
  [this.#selfPattern, this.#pattern] = createState(this, regex, pattern, schema.pattern);
452
452
  // @ts-ignore
453
- [this.#selfValues, this.#values] = createState(this, values, values$1, schema.values);
453
+ [this.#selfValues, this.#values] = createState(this, values$1, values, schema.values);
454
454
 
455
455
  const validatorResult = createValidator(this, schema.validator, validator);
456
456
 
@@ -682,9 +682,9 @@ class Store {
682
682
  /** @readonly @type {Signal.Computed<(Schema.Value.Group | Schema.Value)[] | null>} */
683
683
  #values
684
684
  get selfValues() { return this.#selfValues.get(); }
685
- set selfValues(v) { this.#selfValues.set(values(v)); }
685
+ set selfValues(v) { this.#selfValues.set(values$1(v)); }
686
686
  get values() { return this.#values.get(); }
687
- set values(v) { this.#selfValues.set(values(v)); }
687
+ set values(v) { this.#selfValues.set(values$1(v)); }
688
688
 
689
689
 
690
690
  /** @type {Signal.Computed<string[]>} */
@@ -1184,6 +1184,16 @@ class ArrayStore extends Store {
1184
1184
  // @ts-ignore
1185
1185
  setArrayStore(ArrayStore);
1186
1186
 
1187
+ const numRegex = /^([+-]?(\d(_?\d)*(\.(\d(_?\d)*)?)?|\.\d(_?\d)*)(?:e[+-]?\d(_?\d)*)|0(b[01](?:_?[01])*|o[0-7](?:_?[0-7])*|x[\dA-F](?:_?[\dA-F])*))$/is;
1188
+ /**
1189
+ *
1190
+ * @param {string} t
1191
+ * @returns
1192
+ */
1193
+ function parseNumber(t) {
1194
+ return numRegex.test(t) ? Number(t.replaceAll('_', '')) : NaN;
1195
+ }
1196
+
1187
1197
  const errors = {
1188
1198
  CALC: 'no `createCalc` option, no expression parsing support',
1189
1199
  EVENT: 'no `createEvent`, options, no event parsing support',
@@ -1231,64 +1241,137 @@ class ParseError extends Error {
1231
1241
  /** @import * as Layout from './index.mjs' */
1232
1242
 
1233
1243
  const attrPattern = /^(?<decorator>[:@!+*\.?]|style:|样式:)?(?<name>-?[\w\p{Unified_Ideograph}_][-\w\p{Unified_Ideograph}_:\d\.]*)$/u;
1244
+ const enhancementPattern = /^~(?<enhancement>[\w\p{Unified_Ideograph}_][-\w\p{Unified_Ideograph}_\d\.]*)(?:(?<decorator>[:@!])(?<name>-?[\w\p{Unified_Ideograph}_][-\w\p{Unified_Ideograph}_:\d\.]*))?$/u;
1234
1245
  const nameRegex = /^(?<name>[a-zA-Z$\p{Unified_Ideograph}_][\da-zA-Z$\p{Unified_Ideograph}_]*)?$/u;
1246
+
1247
+ /**
1248
+ *
1249
+ * @param {Record<string, Layout.Enhancement>} enhancements
1250
+ * @param {string} name
1251
+ */
1252
+ function getEnhancement(enhancements, name) {
1253
+ const enhancement = enhancements[name];
1254
+ if (enhancement) { return enhancement; }
1255
+ /** @type {Layout.Enhancement} */
1256
+ const e = {
1257
+ attrs: Object.create(null),
1258
+ events: Object.create(null),
1259
+ };
1260
+ enhancements[name] = e;
1261
+ return e;
1262
+ }
1263
+ /**
1264
+ *
1265
+ * @param {string} value
1266
+ * @param {Exclude<Layout.Options['createCalc'], undefined>} createCalc
1267
+ * @returns {Layout.Node.Name | Layout.Node.Calc | Layout.Node.Value}
1268
+ */
1269
+ function parse$1(value, createCalc) {
1270
+ const text = value.replace(/$\s+|\s+$/gs,'');
1271
+ if (text === 'null') { return {value: null} }
1272
+ if (text === 'true') { return {value: true} }
1273
+ if (text === 'false') { return {value: false} }
1274
+ const t = parseNumber(text);
1275
+ if (!Number.isNaN(t)) { return {value: t}; }
1276
+ if (nameRegex.test(text)) { return {name: text} }
1277
+ return {calc: createCalc(value)};
1278
+ }
1279
+
1235
1280
  /**
1236
1281
  * @param {Layout.Node} node
1237
1282
  * @param {Exclude<Layout.Options['createCalc'], undefined>} createCalc
1238
1283
  * @param {Exclude<Layout.Options['createEvent'], undefined>} createEvent
1239
1284
  */
1240
1285
  function createAttributeAdder(node, createCalc, createEvent) {
1241
- const { attrs, directives, events, classes, styles, vars, aliases, params } = node;
1286
+ const { attrs, events, classes, styles, vars, aliases, params, enhancements } = node;
1242
1287
  /**
1243
1288
  * @param {string} qName
1244
1289
  * @param {string} value
1245
1290
  */
1246
1291
  function addAttribute(qName, value) {
1247
- const attr = attrPattern.exec(qName
1248
- .replace(/./g,'.')
1249
- .replace(/:/g,':')
1250
- .replace(/@/g,'@')
1251
- .replace(/+/g,'+')
1252
- .replace(/-/g,'-')
1253
- .replace(/[*×]/g,'*')
1254
- .replace(/!/g, '!'))?.groups;
1292
+ const qn = qName
1293
+ .replace(/./g,'.')
1294
+ .replace(/:/g,':')
1295
+ .replace(/@/g,'@')
1296
+ .replace(/+/g,'+')
1297
+ .replace(/-/g,'-')
1298
+ .replace(/[*×]/g,'*')
1299
+ .replace(/!/g, '!');
1300
+ const attr = (attrPattern.exec(qn) || enhancementPattern.exec(qn))?.groups;
1255
1301
  if (!attr) { throw new ParseError('ATTR', qName); }
1256
- const { name } = attr;
1302
+ const { name, enhancement } = attr;
1257
1303
  const decorator = attr.decorator?.toLowerCase();
1258
- if (!decorator) {
1259
- attrs[name] = value;
1260
- } else if (decorator === ':') {
1261
- attrs[name] = nameRegex.test(value) ? {name: value} : createCalc(value);
1262
- } else if (decorator === '.') {
1263
- classes[name] = !value ? true : nameRegex.test(value) ? value : createCalc(value);
1264
- } else if (decorator === 'style:') {
1265
- styles[name] = nameRegex.test(value) ? value : createCalc(value);
1266
- } else if (decorator === '@') {
1267
- events[name] = nameRegex.test(value) ? value : createEvent(value);
1268
- } else if (decorator === '+') {
1269
- vars[name] = !value ? '' : nameRegex.test(value) ? value : createCalc(value);
1270
- } else if (decorator === '*') {
1271
- aliases[name] = nameRegex.test(value) ? value : createCalc(value);
1272
- } else if (decorator === '?') {
1273
- params[name] = nameRegex.test(value) ? value : createCalc(value);
1274
- } else if (decorator === '!') {
1275
- const key = name.toString();
1276
- switch (key) {
1277
- case 'fragment': directives.fragment = value || true; break;
1278
- case 'else': directives.else = true; break;
1279
- case 'enum':
1280
- directives.enum = value ? nameRegex.test(value) ? value : createCalc(value) : true;
1281
- break;
1282
- case 'if':
1283
- case 'text':
1284
- case 'html':
1285
- directives[key] = nameRegex.test(value) ? value : createCalc(value);
1286
- break;
1287
- case 'template': directives.template = value; break;
1288
- case 'bind': directives.bind = value || true; break;
1289
- case 'value': directives.value = value; break;
1290
- case 'comment': directives.comment = value; break;
1304
+ if (enhancement) {
1305
+ if (decorator === ':') {
1306
+ getEnhancement(enhancements, enhancement).attrs[name] = value ? parse$1(value, createCalc) : {name};
1307
+ } else if (decorator === '@') {
1308
+ getEnhancement(enhancements, enhancement).events[name] = value ? nameRegex.test(value) ? {name: value} : {event: createEvent(value)} : {name};
1309
+ } else if (decorator === '!') {
1310
+ switch(name) {
1311
+ case 'bind':
1312
+ getEnhancement(enhancements, enhancement).bind = value || true;
1313
+ break;
1314
+ }
1315
+ } else {
1316
+ getEnhancement(enhancements, enhancement).value = parse$1(value, createCalc);
1291
1317
  }
1318
+ return;
1319
+ }
1320
+ if (!decorator) {
1321
+ attrs[name] = {value};
1322
+ return;
1323
+ }
1324
+ if (decorator === ':') {
1325
+ attrs[name] = value ? parse$1(value, createCalc) : {name};
1326
+ return;
1327
+ }
1328
+ if (decorator === '.') {
1329
+ classes[name] = value ? parse$1(value, createCalc) : {name};
1330
+ return;
1331
+ }
1332
+ if (decorator === 'style:') {
1333
+ styles[name] = parse$1(value, createCalc);
1334
+ return;
1335
+ }
1336
+ if (decorator === '@') {
1337
+ events[name] = value ? nameRegex.test(value) ? {name: value} : {event: createEvent(value)} : {name};
1338
+ return;
1339
+ }
1340
+ if (decorator === '+') {
1341
+ vars[name] = !value ? {value: undefined} : parse$1(value, createCalc);
1342
+ return;
1343
+ }
1344
+ if (decorator === '*') {
1345
+ aliases[name] = parse$1(value, createCalc);
1346
+ return;
1347
+ }
1348
+ if (decorator === '?') {
1349
+ params[name] = parse$1(value, createCalc);
1350
+ return;
1351
+ }
1352
+ if (decorator !== '!') {
1353
+ return;
1354
+ }
1355
+ const key = name.toString();
1356
+ switch (key) {
1357
+ case 'fragment': node.fragment = value || true; break;
1358
+ case 'else': node.else = true; break;
1359
+ case 'enum':
1360
+ node.enum = value ? parse$1(value, createCalc) : {value: true};
1361
+ break;
1362
+ case 'if':
1363
+ node.if = parse$1(value, createCalc);
1364
+ break;
1365
+ case 'text':
1366
+ node.text = parse$1(value, createCalc);
1367
+ break;
1368
+ case 'html':
1369
+ node.html = parse$1(value, createCalc);
1370
+ break;
1371
+ case 'template': node.template = value; break;
1372
+ case 'bind': node.bind = value || true; break;
1373
+ case 'value': node.value = value; break;
1374
+ case 'comment': node.comment = value; break;
1292
1375
  }
1293
1376
  }
1294
1377
  return addAttribute;
@@ -1309,12 +1392,12 @@ function createElement(name, is) {
1309
1392
  children: [],
1310
1393
  attrs: Object.create(null),
1311
1394
  events: Object.create(null),
1312
- directives: Object.create(null),
1313
1395
  classes: Object.create(null),
1314
1396
  styles: Object.create(null),
1315
1397
  vars: Object.create(null),
1316
1398
  aliases: Object.create(null),
1317
1399
  params: Object.create(null),
1400
+ enhancements: Object.create(null),
1318
1401
  };
1319
1402
  }
1320
1403
 
@@ -1826,6 +1909,54 @@ function _xmlEncoder(c) {
1826
1909
  '&#' + c.charCodeAt() + ';';
1827
1910
  }
1828
1911
 
1912
+ /**
1913
+ * @param {Layout.Node.Name | Layout.Node.Calc | Layout.Node.Event | Layout.Node.Value} def
1914
+ */
1915
+ function toValue({name, calc, event, value}) {
1916
+ if (value === true || value === undefined) { return ''; }
1917
+ const val = typeof value === 'string' ? JSON.stringify(value) : name || calc || event || value;
1918
+ return `="${toAttrValue$1(val)}"`;
1919
+ }
1920
+ /**
1921
+ * @param {string | Function | null} [value]
1922
+ */
1923
+ function toAttrValue$1(value) {
1924
+ return String(value).replace(/[<&"]/g, _xmlEncoder);
1925
+ }
1926
+
1927
+ /**
1928
+ * @param {Record<string, Layout.Node.Name | Layout.Node.Calc | Layout.Node.Event | Layout.Node.Value>} values
1929
+ * @param {string} [prefix]
1930
+ * @param {boolean | null} [isName]
1931
+ */
1932
+ function *values(values, prefix, isName = false) {
1933
+ if (prefix) {
1934
+ for (const [key, {name, calc, event, value}] of Object.entries(values)) {
1935
+ yield ` ${prefix}${key}`;
1936
+ if (isName && name === key) { continue; }
1937
+ const val = value && typeof value === 'string' ? JSON.stringify(value) : name || calc || event || value;
1938
+ if (val == null) { continue; }
1939
+ if (isName === false && val === true) { continue; }
1940
+ yield `="${toAttrValue$1(val)}"`;
1941
+ }
1942
+ return;
1943
+ }
1944
+ for (const [key, attr] of Object.entries(values)) {
1945
+ if (!attr) { continue; }
1946
+ const {name, value, calc} = attr;
1947
+ if (name === key) { yield ` :${key}`; continue; }
1948
+ if (name || calc || typeof value !== 'string') {
1949
+ yield ` :${key}="${toAttrValue$1(name || calc || value)}"`;
1950
+ continue;
1951
+ }
1952
+ yield ` ${key}`;
1953
+ if (value) {
1954
+ yield `="${toAttrValue$1(value)}"`;
1955
+ }
1956
+ }
1957
+ }
1958
+
1959
+
1829
1960
  /**
1830
1961
  *
1831
1962
  * @param {Layout.Node} node
@@ -1833,81 +1964,37 @@ function _xmlEncoder(c) {
1833
1964
  * @returns {Iterable<string>}
1834
1965
  */
1835
1966
  function* nodeToString(node, level = 0) {
1836
- const { attrs, events, directives, children, is, name, params, classes, styles, aliases, vars } = node;
1967
+ const { children, is, name } = node;
1837
1968
  const pad = level > 0 ? ''.padEnd(level, '\t') : '';
1838
1969
 
1839
1970
  yield pad;
1840
1971
  yield* ['<', name || '-'];
1841
1972
  if (is) { yield* ['|', is]; }
1842
-
1843
- for (const [name, value] of Object.entries(params)) {
1844
- if (value == null) { continue; }
1845
- const val = typeof value === 'function' ? String(value) : value;
1846
- yield* [' ?', name];
1847
- if (val && typeof val === 'string') {
1848
- yield* ['="', val.replace(/[<&"]/g, _xmlEncoder), '"'];
1849
- }
1850
- }
1851
- for (const [name, value] of Object.entries(directives)) {
1852
- if (value === false || value == null) { continue; }
1853
- const val = typeof value === 'function' ? String(value) : value;
1854
- yield* [' !', name];
1855
- if (val && typeof val === 'string') {
1856
- yield* ['="', val.replace(/[<&"]/g, _xmlEncoder), '"'];
1857
- }
1858
- }
1859
- for (const [name, value] of Object.entries(aliases)) {
1860
- if (value == null) { continue; }
1861
- const val = typeof value === 'function' ? String(value) : value;
1862
- yield* [' *', name];
1863
- if (val && typeof val === 'string') {
1864
- yield* ['="', val.replace(/[<&"]/g, _xmlEncoder), '"'];
1865
- }
1866
- }
1867
- for (const [name, value] of Object.entries(vars)) {
1868
- if (value == null) { continue; }
1869
- const val = typeof value === 'function' ? String(value) : value;
1870
- yield* [' +', name];
1871
- if (val && typeof val === 'string') {
1872
- yield* ['="', val.replace(/[<&"]/g, _xmlEncoder), '"'];
1873
- }
1874
- }
1875
- for (const [name, value] of Object.entries(attrs)) {
1876
- if (value == null) { continue; }
1877
- if (typeof value === 'string') {
1878
- yield* [' ', name];
1879
- if (value) {
1880
- yield* ['="', value.replace(/[<&"]/g, _xmlEncoder), '"'];
1881
- }
1882
- continue;
1883
- }
1884
- const val = typeof value === 'function' ? String(value) : typeof value === 'object' ? value.name : value;
1885
- if (val && typeof val === 'string') {
1886
- yield* [' :', name, '="', val.replace(/[<&"]/g, _xmlEncoder) || '', '"'];
1887
- }
1973
+ if (node.template) {
1974
+ yield ` !template="${toAttrValue$1(node.template)}"`;
1975
+ yield* values(node.params, '?', null);
1888
1976
  }
1889
- for (const [name, value] of Object.entries(events)) {
1890
- if (value == null) { continue; }
1891
- const val = typeof value === 'function' ? String(value) : value;
1892
- if (val && typeof val === 'string') {
1893
- yield* [' @', name, '="', val.replace(/[<&"]/g, _xmlEncoder), '"'];
1894
- }
1895
- }
1896
- for (const [name, value] of Object.entries(classes)) {
1897
- if (value == null || value == false) { continue; }
1898
- const val = typeof value === 'function' ? String(value) : value;
1899
- yield* [' .', name];
1900
- if (val && typeof val === 'string') {
1901
- yield* [' .', name, '="', val.replace(/[<&"]/g, _xmlEncoder), '"'];
1902
- }
1903
- }
1904
- for (const [name, value] of Object.entries(styles)) {
1905
- if (value == null) { continue; }
1906
- const val = typeof value === 'function' ? String(value) : value;
1907
- if (val && typeof val === 'string') {
1908
- yield* [' style:', name, '="', val.replace(/[<&"]/g, _xmlEncoder), '"'];
1909
- }
1977
+ if (node.fragment) { yield node.fragment === true ? ` !fragment` : ` !fragment="${toAttrValue$1(node.fragment)}"`; }
1978
+ if (node.else) { yield ` !else`; }
1979
+ if (node.if) { yield ` !if${toValue(node.if)}`; }
1980
+ if (node.value) { yield ` !value="${toAttrValue$1(node.value)}"`; }
1981
+ if (node.enum) { yield ` !enum${toValue(node.enum)}`; }
1982
+ yield* values(node.aliases, '*', null);
1983
+ yield* values(node.vars, '+', null);
1984
+ if (node.bind) { yield node.bind === true ? ` !bind` : ` !bind="${toAttrValue$1(node.bind)}"`; }
1985
+ yield* values(node.attrs);
1986
+ yield* values(node.events, '@', true);
1987
+ yield* values(node.classes, '.', true);
1988
+ yield* values(node.styles, 'style:');
1989
+ for (const [k, en] of Object.entries(node.enhancements)) {
1990
+ if (en.bind) { yield en.bind === true ? ` ~${k}!bind` : ` ~${k}!bind="${toAttrValue$1(en.bind)}"`; }
1991
+ if (en.value) { yield ` ~${k}${toValue(en.value)}`; }
1992
+ yield* values(en.attrs, `~${k}:`, true);
1993
+ yield* values(en.events, `~${k}@`, true);
1910
1994
  }
1995
+ if (node.text) { yield ` !text${toValue(node.text)}`; }
1996
+ if (node.html) { yield ` !html${toValue(node.html)}`; }
1997
+ if (node.comment) { yield ` !comment="${toAttrValue$1(node.comment)}"`; }
1911
1998
  if (!children.length) {
1912
1999
  yield '/>';
1913
2000
  if (level >= 0) { yield '\n'; }
@@ -1969,44 +2056,66 @@ function toString(value, formable) {
1969
2056
  /**
1970
2057
  * @typedef {object} Directives
1971
2058
  *
1972
- * @property {string} [template]
1973
- *
1974
- * @property {boolean | string} [fragment]
1975
- *
1976
- * @property {string | Calc} [if]
1977
- * @property {boolean} [else]
1978
- *
1979
- * @property {string} [value] 值关联(关联为列表)
1980
- * @property {boolean | string | Calc} [enum] 列表属性枚举
1981
- *
1982
- * @property {boolean | string} [bind]
1983
- * @property {string | Calc} [text]
1984
- * @property {string | Calc} [html]
1985
- *
1986
- * @property {string} [comment] 注释
1987
2059
  */
1988
2060
 
1989
2061
 
1990
2062
 
2063
+ /**
2064
+ * @typedef {object} Enhancement
2065
+ * @property {Record<string, Node.Name | Node.Event>} events
2066
+ * @property {Record<string, Node.Name | Node.Calc | Node.Value>} attrs
2067
+ * @property {Node.Name | Node.Calc | Node.Value} [value]
2068
+ * @property {boolean | string} [bind]
2069
+ */
2070
+
1991
2071
  /**
1992
2072
  * @typedef {object} Options
1993
2073
  * @property {(t: string) => Calc} [options.createCalc]
1994
2074
  * @property {(t: string) => EventListener} [options.createEvent]
1995
2075
  * @property {Set<string>} [options.simpleTag]
1996
2076
  */
2077
+ /**
2078
+ * @template [T=any]
2079
+ * @typedef {{value: T; name?: undefined; calc?: undefined; event?: undefined}} Node.Value
2080
+ */
2081
+ /**
2082
+ * @typedef {{name: string; value?: undefined; calc?: undefined; event?: undefined}} Node.Name
2083
+ */
2084
+ /**
2085
+ * @typedef {{event: EventListener; name?: undefined; calc?: undefined; value?: undefined}} Node.Event
2086
+ */
2087
+ /**
2088
+ * @typedef {{calc: Calc; name?: undefined; value?: undefined; event?: undefined}} Node.Calc
2089
+ */
1997
2090
  /**
1998
2091
  * @typedef {object} Node
1999
2092
  * @property {string} name
2000
2093
  * @property {string?} [is]
2001
2094
  * @property {string} [id]
2002
- * @property {Record<string, string | {name: string} | Calc>} attrs
2003
- * @property {Record<string, string | Calc>} params
2004
- * @property {Record<string, string | boolean | Calc>} classes
2005
- * @property {Record<string, string | Calc>} styles
2006
- * @property {Record<string, string | EventListener>} events
2007
- * @property {Record<string, string | Calc>} vars
2008
- * @property {Record<string, string | Calc>} aliases
2009
- * @property {Directives} directives
2095
+ * @property {Record<string, Node.Name | Node.Calc | Node.Value>} attrs
2096
+ * @property {Record<string, Node.Name | Node.Calc | Node.Value>} params
2097
+ * @property {Record<string, Node.Name | Node.Calc | Node.Value>} classes
2098
+ * @property {Record<string, Node.Name | Node.Calc | Node.Value>} styles
2099
+ * @property {Record<string, Node.Name | Node.Event>} events
2100
+ * @property {Record<string, Node.Name | Node.Calc | Node.Value>} vars
2101
+ * @property {Record<string, Node.Name | Node.Calc | Node.Value>} aliases
2102
+ * @property {Record<string, Enhancement>} enhancements
2103
+ *
2104
+ * @property {string} [template]
2105
+ * @property {boolean | string} [fragment]
2106
+ *
2107
+ * @property {Node.Name | Node.Calc | Node.Value} [if]
2108
+ * @property {boolean} [else]
2109
+ *
2110
+ * @property {string} [value] 值关联
2111
+ * @property {Node.Name | Node.Calc | Node.Value} [enum] 列表属性枚举
2112
+ *
2113
+ * @property {boolean | string} [bind]
2114
+ * @property {Node.Name | Node.Value | Node.Calc} [text]
2115
+ * @property {Node.Name | Node.Value | Node.Calc} [html]
2116
+ *
2117
+ * @property {string} [comment] 注释
2118
+ *
2010
2119
  * @property {(Node | string)[]} children
2011
2120
  */
2012
2121
 
@@ -2014,7 +2123,7 @@ function toString(value, formable) {
2014
2123
  * @callback Calc
2015
2124
  * @param {Record<string, any>} env
2016
2125
  * @returns {any}
2017
- */
2126
+ */
2018
2127
 
2019
2128
 
2020
2129
  /**
@@ -2311,37 +2420,63 @@ function addStore(store, env) {
2311
2420
  */
2312
2421
  class Environment {
2313
2422
  /**
2314
- * @param {string | Layout.Calc} value
2423
+ * @param {Layout.Node.Name | Layout.Node.Calc | Layout.Node.Value} value
2315
2424
  */
2316
- exec(value) {
2317
- if (typeof value === 'string') {
2318
- const item = this.#items[value];
2425
+ exec({name, calc, value }) {
2426
+ if (typeof name === 'string') {
2427
+ const item = this.#items[name];
2319
2428
  if (typeof item?.get !== 'function') { return }
2320
2429
  return item.get();
2321
2430
  }
2322
- if (typeof value === 'function') {
2323
- return value(this.getters);
2431
+ if (typeof calc === 'function') {
2432
+ return calc(this.getters);
2324
2433
  }
2434
+ return value;
2325
2435
  }
2326
2436
  /**
2327
- * @param {string | Layout.Calc} value
2437
+ * @param {Layout.Node.Name | Layout.Node.Calc | Layout.Node.Value} value
2438
+ */
2439
+ get({name, calc, value}) {
2440
+ if (typeof name === 'string') {
2441
+ const item = this.#items[name];
2442
+ if (typeof item?.get !== 'function') { return }
2443
+ const{get, set} = item;
2444
+ return {get, set};
2445
+ }
2446
+ if (typeof calc === 'function') {
2447
+ const c = new Signal.Computed(() => calc(this.getters));
2448
+ return {get: () => c.get() };
2449
+ }
2450
+ return {get: () => value };
2451
+ }
2452
+ /**
2453
+ * @param {Layout.Node.Name | Layout.Node.Calc | Layout.Node.Value} value
2328
2454
  * @param {(value: any) => void} cb
2329
2455
  */
2330
2456
  watch(value, cb) { return watch(() => this.exec(value), cb, true); }
2331
2457
 
2332
2458
  /**
2333
- * @param {string | Layout.Calc | boolean | null} [name]
2459
+ * @param {Layout.Node.Name | Layout.Node.Calc | Layout.Node.Value | null} [en]
2334
2460
  */
2335
- enum(name) {
2336
- if (!name) { return true; }
2337
- if (name === true) { return this.store; }
2338
- if (typeof name === 'function') { return () => name(this.getters); }
2339
- if (typeof name !== 'string') { return null; }
2340
- const item = this.#items[name];
2341
- if (typeof item?.get !== 'function') { return null }
2342
- const store = item.store;
2343
- return store instanceof Store ? store : item.get;
2344
-
2461
+ enum(en) {
2462
+ if (!en) { return true; }
2463
+ const {name, calc} = en;
2464
+ if (typeof calc === 'function') { return () => calc(this.getters); }
2465
+ if (typeof name === 'string') {
2466
+ const item = this.#items[name];
2467
+ if (typeof item?.get !== 'function') { return null }
2468
+ const store = item.store;
2469
+ return store instanceof Store ? store : item.get;
2470
+ }
2471
+ return this.store;
2472
+ }
2473
+ /**
2474
+ * @param {string | boolean | null} [name]
2475
+ */
2476
+ getStore(name) {
2477
+ if (!name) { return null; }
2478
+ const item = this.#items[name === true ? '' : name];
2479
+ return item?.get && item.store || null
2345
2480
  }
2346
2481
 
2347
2482
  /**
@@ -2384,6 +2519,29 @@ class Environment {
2384
2519
  ]));
2385
2520
  return res;
2386
2521
  }
2522
+ /**
2523
+ * @param {string | true} name
2524
+ * @returns {Record<string, {get(): any; set?(v: any): void}> | void}
2525
+ */
2526
+ getBindAll(name) {
2527
+ const item = this.#items[name === true ? '' : name];
2528
+ if (!item?.get) { return {}; }
2529
+ const { store } = item;
2530
+ if (!store) {
2531
+ const { get, set } = item;
2532
+ return { '$value': {get,set} }
2533
+ }
2534
+ /** @type {Record<string, {get(): any; set?(v: any): void}> | void} */
2535
+ const res = Object.fromEntries([...bindableSet].map(v => [
2536
+ `$${v}`, v === 'value' || v === 'state' ? {
2537
+ get: () => store[v],
2538
+ set: (s)=>{store[v] = s;}
2539
+ } : {
2540
+ get: () => store[v],
2541
+ }
2542
+ ]));
2543
+ return res;
2544
+ }
2387
2545
  /**
2388
2546
  * @param {string | true} name
2389
2547
  * @param {string} type
@@ -2427,12 +2585,12 @@ class Environment {
2427
2585
  }
2428
2586
 
2429
2587
  /**
2430
- * @param {string | Layout.EventListener} event
2588
+ * @param {Layout.Node.Name | Layout.Node.Event} event
2431
2589
  * @returns {Layout.EventListener?}
2432
2590
  */
2433
- getEvent(event) {
2591
+ getEvent({name, event}) {
2434
2592
  if (typeof event === 'function') { return event }
2435
- const item = this.#items[event];
2593
+ const item = this.#items[name];
2436
2594
  if (!item) { return null }
2437
2595
  const {exec, calc} = item;
2438
2596
  if (typeof exec === 'function') { return exec }
@@ -2550,45 +2708,55 @@ class Environment {
2550
2708
  const items = cloned.#items;
2551
2709
  for (const [key, param] of Object.entries(params)) {
2552
2710
  const attr = key in attrs ? attrs[key] : null;
2553
- if (typeof attr === 'string') {
2554
- explicit[key] = items[key] = {get: () => attr};
2555
- } else if (attr && typeof attr === 'object') {
2556
- const item = sourceEnv.#items[attr.name];
2557
- if (!item?.get) { continue; }
2558
- if (!item.store) {
2559
- explicit[key] = items[key] = item;
2711
+ if (attr) {
2712
+ const {name, calc, value} = attr;
2713
+ if (name) {
2714
+ const item = sourceEnv.#items[name];
2715
+ if (!item?.get) { continue; }
2716
+ if (!item.store) {
2717
+ explicit[key] = items[key] = item;
2718
+ continue;
2719
+ }
2720
+ for (const [k, it] of toItem(item.store, key)) {
2721
+ explicit[k] = items[k] = it;
2722
+ }
2560
2723
  continue;
2561
- }
2562
- for (const [k, it] of toItem(item.store, key)) {
2563
- explicit[k] = items[k] = it;
2724
+ } else if (typeof calc === 'function') {
2725
+ const val = new Signal.Computed(() => calc(sourceEnv.getters));
2726
+ explicit[key] = items[key] = {
2727
+ get: () => { return val.get(); },
2728
+ };
2729
+ continue;
2730
+ } else {
2731
+ explicit[key] = items[key] = {get: () => value};
2564
2732
  }
2565
2733
  continue;
2566
-
2567
- } else if (typeof attr === 'function') {
2568
- const val = new Signal.Computed(() => attr(sourceEnv.getters));
2569
- explicit[key] = items[key] = {
2570
- get: () => { return val.get(); },
2571
- };
2572
- continue;
2573
- } else if (typeof param === 'function') {
2574
- const getters = cloned.getters;
2575
- cloned.#getters = null;
2576
- const val = new Signal.Computed(() => param(getters));
2577
- explicit[key] = items[key] = {
2578
- get: () => { return val.get(); },
2579
- };
2580
- continue;
2581
2734
  } else {
2582
- const item = items[param];
2583
- if (!item?.get) { continue; }
2584
- if (!item.store) {
2585
- explicit[key] = items[key] = item;
2735
+ const {name, calc, value} = param;
2736
+ if (typeof calc === 'function') {
2737
+ const getters = cloned.getters;
2738
+ cloned.#getters = null;
2739
+ const val = new Signal.Computed(() => calc(getters));
2740
+ explicit[key] = items[key] = {
2741
+ get: () => { return val.get(); },
2742
+ };
2586
2743
  continue;
2744
+ } else if (name) {
2745
+ const item = items[name];
2746
+ if (!item?.get) { continue; }
2747
+ if (!item.store) {
2748
+ explicit[key] = items[key] = item;
2749
+ continue;
2750
+ }
2751
+ for (const [k, it] of toItem(item.store, key)) {
2752
+ explicit[k] = items[k] = it;
2753
+ }
2754
+ continue;
2755
+ } else {
2756
+ explicit[key] = items[key] = {get: () => value};
2757
+ continue;
2758
+
2587
2759
  }
2588
- for (const [k, it] of toItem(item.store, key)) {
2589
- explicit[k] = items[k] = it;
2590
- }
2591
- continue;
2592
2760
  }
2593
2761
  }
2594
2762
  return cloned;
@@ -2601,8 +2769,8 @@ class Environment {
2601
2769
  }
2602
2770
  /**
2603
2771
  *
2604
- * @param {Record<string, string | Layout.Calc>} aliases
2605
- * @param {Record<string, string | Layout.Calc>} vars
2772
+ * @param {Record<string, Layout.Node.Name | Layout.Node.Calc | Layout.Node.Value>} aliases
2773
+ * @param {Record<string, Layout.Node.Name | Layout.Node.Calc | Layout.Node.Value>} vars
2606
2774
  */
2607
2775
  set(aliases, vars) {
2608
2776
  if (Object.keys(aliases).length + Object.keys(vars).length === 0) { return this; }
@@ -2611,35 +2779,40 @@ class Environment {
2611
2779
  cloned.#object = this.#object;
2612
2780
  const explicit = cloned.#explicit;
2613
2781
  const items = cloned.#items;
2614
- for (const [key, name] of Object.entries(aliases)) {
2615
- if (typeof name === 'function') {
2782
+ for (const [key, {name, calc, value}] of Object.entries(aliases)) {
2783
+ if (typeof calc === 'function') {
2616
2784
  const getters = cloned.getters;
2617
2785
  cloned.#getters = null;
2618
- const val = new Signal.Computed(() => name(getters));
2786
+ const val = new Signal.Computed(() => calc(getters));
2619
2787
  explicit[key] = items[key] = {
2620
2788
  get: () => { return val.get(); },
2621
2789
  };
2622
2790
  continue;
2623
2791
  }
2624
- const item = items[name];
2625
- if (!item) { continue; }
2626
- if (!item.get || !item.store) {
2627
- explicit[key] = items[key] = item;
2792
+ if (name) {
2793
+ const item = items[name];
2794
+ if (!item) { continue; }
2795
+ if (!item.get || !item.store) {
2796
+ explicit[key] = items[key] = item;
2797
+ continue;
2798
+ }
2799
+ for (const [k, it] of toItem(item.store, key)) {
2800
+ explicit[k] = items[k] = it;
2801
+ }
2628
2802
  continue;
2629
2803
  }
2630
- for (const [k, it] of toItem(item.store, key)) {
2631
- explicit[k] = items[k] = it;
2632
- }
2804
+ explicit[key] = items[key] = { get: () => { return value; } };
2805
+ continue;
2633
2806
  }
2634
- for (const [k,v] of Object.entries(vars)) {
2807
+ for (const [k,{name, calc, value}] of Object.entries(vars)) {
2635
2808
 
2636
- const val = new Signal.State(/** @type {any} */(null));
2637
- if (typeof v === 'function') {
2809
+ const val = new Signal.State(/** @type {any} */(value));
2810
+ if (typeof calc === 'function') {
2638
2811
  const settable = cloned.settable;
2639
2812
  cloned.#settable = null;
2640
- val.set(v(settable));
2641
- } else if (v && typeof v === 'string') {
2642
- const item = items[v];
2813
+ val.set(calc(settable));
2814
+ } else if (name) {
2815
+ const item = items[name];
2643
2816
  if (!item?.get) { continue }
2644
2817
  val.set(item.get());
2645
2818
  }
@@ -2750,44 +2923,44 @@ class Environment {
2750
2923
  /**
2751
2924
  * @param {Component.Handler} handler
2752
2925
  * @param {Environment} envs
2753
- * @param {Record<string, string | {name: string} | Layout.Calc>} attrs
2926
+ * @param {Record<string, Layout.Node.Name | Layout.Node.Calc | Layout.Node.Value>} attrs
2754
2927
  * @param {Record<string, Component.Attr>} componentAttrs
2755
2928
  * @param {string | boolean | null} [bindValue]
2756
2929
  */
2757
2930
  function bindAttrs(handler, envs, attrs, componentAttrs, bindValue) {
2758
2931
 
2759
2932
  let bk = new Set();
2760
- for (const [name, attr] of Object.entries(componentAttrs)) {
2761
- const attrValue = attrs[name];
2762
- if (name in attrs) {
2763
- if (typeof attrValue !== 'function' && typeof attrValue !== 'object') {
2764
- handler.set(name, attrValue);
2933
+ for (const [key, attr] of Object.entries(componentAttrs)) {
2934
+ if (key === 'class' || key === 'style') { continue; }
2935
+ if (key in attrs) {
2936
+ const attrDefine = attrs[key];
2937
+ const { name, calc, value } = attrs[key];
2938
+ if (!name && !calc) {
2939
+ handler.set(key, value);
2765
2940
  continue;
2766
2941
  }
2767
- const attrSchema = typeof attrValue === 'function' ? /** @type{Layout.Calc} */(attrValue) : attrValue.name;
2768
2942
  if (attr.immutable) {
2769
- handler.set(name, envs.exec(attrSchema));
2770
- continue;
2943
+ handler.set(key, envs.exec(attrDefine));
2771
2944
  }
2772
- bk.add(envs.watch(attrSchema, v => handler.set(name, v)));
2945
+ bk.add(envs.watch(attrDefine, v => handler.set(key, v)));
2773
2946
  continue;
2774
2947
  }
2775
2948
  const bind = attr.bind;
2776
2949
  if (!bindValue || !bind) {
2777
- handler.set(name, attr.default);
2950
+ handler.set(key, attr.default);
2778
2951
  continue;
2779
2952
  }
2780
2953
  if (typeof bind === 'string') {
2781
- const r = envs.bind(bindValue, bind, v => handler.set(name, v));
2954
+ const r = envs.bind(bindValue, bind, v => handler.set(key, v));
2782
2955
  if (r) {
2783
2956
  bk.add(r);
2784
2957
  } else {
2785
- handler.set(name, attr.default);
2958
+ handler.set(key, attr.default);
2786
2959
  }
2787
2960
  continue;
2788
2961
  }
2789
2962
  if (!Array.isArray(bind)) {
2790
- handler.set(name, attr.default);
2963
+ handler.set(key, attr.default);
2791
2964
  continue;
2792
2965
  }
2793
2966
  const [event, set, isState] = bind;
@@ -2796,11 +2969,11 @@ function bindAttrs(handler, envs, attrs, componentAttrs, bindValue) {
2796
2969
  }
2797
2970
  if (!isState) {
2798
2971
  const bindKey = bindValue === true ? '' : bindValue;
2799
- bk.add(envs.watch(bindKey, v => handler.set(name, v)));
2972
+ bk.add(envs.watch({name: bindKey}, v => handler.set(key, v)));
2800
2973
  handler.addEvent(event, (...args) => { envs.all[bindKey] = set(...args);});
2801
2974
  continue;
2802
2975
  }
2803
- const r = envs.bind(bindValue, 'state', v => handler.set(name, v));
2976
+ const r = envs.bind(bindValue, 'state', v => handler.set(key, v));
2804
2977
  if (!r) { continue; }
2805
2978
  bk.add(r);
2806
2979
  const s = envs.bindSet(bindValue, 'state');
@@ -2824,23 +2997,24 @@ function bindAttrs(handler, envs, attrs, componentAttrs, bindValue) {
2824
2997
  /**
2825
2998
  * @param {Component.Handler} handler
2826
2999
  * @param {Environment} envs
2827
- * @param {Record<string, string | {name: string} | Layout.Calc>} attrs
3000
+ * @param {Record<string, Layout.Node.Name | Layout.Node.Calc | Layout.Node.Value>} attrs
2828
3001
  */
2829
3002
  function bindBaseAttrs(handler, envs, attrs) {
2830
3003
  const tag = handler.tag;
2831
3004
  let bk = new Set();
2832
- for (const [name, attr] of Object.entries(attrs)) {
2833
- if (typeof attr !== 'function' && typeof attr !== 'object') {
2834
- handler.set(name, attr);
3005
+ for (const [key, attr] of Object.entries(attrs)) {
3006
+ if (key === 'class' || key === 'style') { continue; }
3007
+ const {name, calc, value} = attr;
3008
+ if (!name && !calc) {
3009
+ handler.set(key, value);
2835
3010
  continue;
2836
3011
  }
2837
- const attrSchema = typeof attr === 'function' ? /** @type{Layout.Calc} */(attr) : attr.name;
2838
- if (typeof tag === 'string' && tag.toLocaleLowerCase() === 'input' && name.toLocaleLowerCase() === 'type') {
2839
- const value = envs.exec(attrSchema);
2840
- handler.set(name, value);
3012
+ if (typeof tag === 'string' && tag.toLocaleLowerCase() === 'input' && key.toLocaleLowerCase() === 'type') {
3013
+ const value = envs.exec(attr);
3014
+ handler.set(key, value);
2841
3015
  continue;
2842
3016
  }
2843
- bk.add(envs.watch(attrSchema, val => handler.set(name, val)));
3017
+ bk.add(envs.watch(attr, val => handler.set(key, val)));
2844
3018
  }
2845
3019
 
2846
3020
  return ()=> {
@@ -2858,26 +3032,29 @@ function bindBaseAttrs(handler, envs, attrs) {
2858
3032
  /**
2859
3033
  * @param {Node} node
2860
3034
  * @param {Environment} envs
2861
- * @param {Record<string, string | boolean | Layout.Calc>} classes
3035
+ * @param {Record<string, Layout.Node.Name | Layout.Node.Calc | Layout.Node.Value>} classes
3036
+ * @param {Layout.Node.Name | Layout.Node.Calc | Layout.Node.Value} [classAttr]
2862
3037
  */
2863
- function bindClasses(node, classes, envs) {
3038
+ function bindClasses(node, envs, classes, classAttr) {
2864
3039
  if (!(node instanceof Element)) {
2865
3040
  return () => {};
2866
3041
  }
3042
+ if (classAttr) {
3043
+ node.className += ' ' + envs.exec(classAttr);
3044
+ }
2867
3045
 
2868
3046
  /** @type {Set<() => void>?} */
2869
3047
  let bk = new Set();
2870
- for (const [name, attr] of Object.entries(classes)) {
2871
- if (!attr) { continue; }
2872
- if (attr === true) {
2873
- node.classList.add(name);
3048
+ for (const [key, attr] of Object.entries(classes)) {
3049
+ if (attr.value) {
3050
+ node.classList.add(key);
2874
3051
  continue;
2875
3052
  }
2876
3053
  bk.add(watch(() => Boolean(envs.exec(attr)), value => {
2877
3054
  if (value) {
2878
- node.classList.add(name);
3055
+ node.classList.add(key);
2879
3056
  } else {
2880
- node.classList.remove(name);
3057
+ node.classList.remove(key);
2881
3058
  }
2882
3059
  }, true));
2883
3060
  }
@@ -2961,16 +3138,20 @@ function toStyle(name, value) {
2961
3138
  /**
2962
3139
  * @param {Element} node
2963
3140
  * @param {Environment} envs
2964
- * @param {Record<string, string | Layout.Calc>} classes
3141
+ * @param {Record<string, Layout.Node.Name | Layout.Node.Calc | Layout.Node.Value>} styles
3142
+ * @param {Layout.Node.Name | Layout.Node.Calc | Layout.Node.Value} [styleAttr]
2965
3143
  */
2966
- function bindStyles(node, classes, envs) {
3144
+ function bindStyles(node, envs, styles, styleAttr) {
2967
3145
  if (!(node instanceof HTMLElement) && !(node instanceof SVGElement)) {
2968
3146
  return () => {};
2969
3147
  }
3148
+ if (styleAttr) {
3149
+ node.setAttribute('style', node.getAttribute('style') + ';' + envs.exec(styleAttr));
3150
+ }
2970
3151
 
2971
3152
  /** @type {Set<() => void>?} */
2972
3153
  let bk = new Set();
2973
- for (const [name, attr] of Object.entries(classes)) {
3154
+ for (const [name, attr] of Object.entries(styles)) {
2974
3155
  bk.add(watch(() => toStyle(name, envs.exec(attr)), value => {
2975
3156
  if (value) {
2976
3157
  node.style.setProperty(name, ...value);
@@ -3032,9 +3213,10 @@ class EventEmitter {
3032
3213
  }
3033
3214
  }
3034
3215
 
3035
- /** @import { Component } from '../types.mjs' */
3216
+ /** @import { Component, Relatedness } from '../types.mjs' */
3036
3217
  /** @import Store from '../Store/index.mjs' */
3037
3218
  /** @import Environment from './Environment/index.mjs' */
3219
+ /** @import * as Layout from '../Layout/index.mjs' */
3038
3220
 
3039
3221
 
3040
3222
  /** @type {Record<string, Component.Event.Filter>} */
@@ -3154,14 +3336,68 @@ const eventFilters = {
3154
3336
  }
3155
3337
  },
3156
3338
  };
3339
+ /**
3340
+ *
3341
+ * @param {string[]} fs
3342
+ * @param {Record<string, string | Component.Event.Filter>} filters
3343
+ * @param {AddEventListenerOptions?} [options]
3344
+ * @returns
3345
+ */
3346
+ function findFilters(fs, filters, options) {
3347
+ /** @type {[Component.Event.Filter, string[], boolean][]} */
3348
+ const filterFns = [];
3349
+ if (filters) for (let f = fs.shift(); f; f = fs.shift()) {
3350
+ const paramIndex = f.indexOf(':');
3351
+ const noParamName = paramIndex >= 0 ? f.slice(0, paramIndex) : f;
3352
+ const param = paramIndex >= 0 ? f.slice(paramIndex + 1).split(':') : [];
3353
+ const filterName = noParamName.replace(/^-+/, '');
3354
+ const sub = (noParamName.length - filterName.length) % 2 === 1;
3355
+ let filter = filters[filterName] || filterName;
3356
+ if (options) {
3357
+ switch (filter) {
3358
+ case 'once': case 'passive': case 'capture': options[filter] = !sub; continue;
3359
+ }
3360
+ }
3361
+ if (typeof filter === 'string') {
3362
+ filter = eventFilters[filter];
3363
+ }
3364
+ if (typeof filter !== 'function') { continue; }
3365
+ filterFns.push([filter, param, sub]);
3366
+ }
3367
+ return filterFns;
3368
+ }
3369
+
3370
+ /**
3371
+ *
3372
+ * @param {Environment} env
3373
+ * @param {Layout.EventListener} fn
3374
+ * @param {[Component.Event.Filter, string[], boolean][]} filterFns
3375
+ * @returns {($event: any) => void}
3376
+ */
3377
+ function bindFilters(env, fn, filterFns) {
3378
+ return $event => {
3379
+ const global = env.all;
3380
+ for (const [filter, param, sub] of filterFns) {
3381
+ if (filter($event, param, global) === sub) { return; }
3382
+ }
3383
+ fn($event, global);
3384
+ }
3385
+ }
3386
+
3387
+ /** @import { Component, Relatedness } from '../types.mjs' */
3388
+ /** @import Store from '../Store/index.mjs' */
3389
+ /** @import Environment from './Environment/index.mjs' */
3390
+
3391
+
3157
3392
  /**
3158
3393
  *
3159
3394
  * @param {Component | string} component
3160
3395
  * @param {Environment} env
3161
- * @param {((store: Store, el: Element) => () => void)?} [relate]
3396
+ * @param {Store?} store
3397
+ * @param {((store: Store, el: Element | Relatedness) => () => void)?} [relate]
3162
3398
  * @returns
3163
3399
  */
3164
- function createContext(component, env, relate) {
3400
+ function createContext(component, env, store, relate) {
3165
3401
  const tag = typeof component === 'string' ? component : component.tag;
3166
3402
  const { attrs, events } = typeof component !== 'string' && component || { attrs: null, events: null };
3167
3403
 
@@ -3202,9 +3438,9 @@ function createContext(component, env, relate) {
3202
3438
  };
3203
3439
  },
3204
3440
  relate(el) {
3205
- if (!relate || destroyed) { return () => { }; }
3441
+ if (!store || !relate || destroyed) { return () => { }; }
3206
3442
  try {
3207
- const w = relate(env.store, el);
3443
+ const w = relate(store, el);
3208
3444
  if (typeof w !== 'function') { return () => { }; }
3209
3445
  cancelFns.add(w);
3210
3446
  return () => {
@@ -3245,40 +3481,8 @@ function createContext(component, env, relate) {
3245
3481
  if (!filters) { return; }
3246
3482
  /** @type {AddEventListenerOptions} */
3247
3483
  const options = {};
3248
- /** @type {[Component.Event.Filter, string[], boolean][]} */
3249
- const filterFns = [];
3250
- if (filters) for (let f = fs.shift(); f; f = fs.shift()) {
3251
- const paramIndex = f.indexOf(':');
3252
- const noParamName = paramIndex >= 0 ? f.slice(0, paramIndex) : f;
3253
- const param = paramIndex >= 0 ? f.slice(paramIndex + 1).split(':') : [];
3254
- const filterName = noParamName.replace(/^-+/, '');
3255
- const sub = (noParamName.length - filterName.length) % 2 === 1;
3256
- let filter = filters[filterName] || filterName;
3257
- switch (filter) {
3258
- case 'once':
3259
- options.once = !sub;
3260
- break;
3261
- case 'passive':
3262
- options.passive = !sub;
3263
- break;
3264
- case 'capture':
3265
- options.capture = !sub;
3266
- break;
3267
- default:
3268
- if (typeof filter === 'string') {
3269
- filter = eventFilters[filter];
3270
- }
3271
- }
3272
- if (typeof filter !== 'function') { continue; }
3273
- filterFns.push([filter, param, sub]);
3274
- }
3275
- allEvents.push([e, $event => {
3276
- const global = env.all;
3277
- for (const [filter, param, sub] of filterFns) {
3278
- if (filter($event, param, global) === sub) { return; }
3279
- }
3280
- fn($event, global);
3281
- }, options]);
3484
+ const filterFns = findFilters(fs, filters, options);
3485
+ allEvents.push([e, bindFilters(env, fn, filterFns), options]);
3282
3486
  },
3283
3487
  destroy() {
3284
3488
  if (destroyed) { return; }
@@ -3468,6 +3672,9 @@ const tagBindMap = {
3468
3672
  function createTagComponent (context, name, is) {
3469
3673
  const node = document.createElement(name, {is: is || undefined});
3470
3674
  const { watchAttr, props } = context;
3675
+ if(['input', 'textarea', 'select'].includes(name.toLowerCase())) {
3676
+ context.relate(node);
3677
+ }
3471
3678
 
3472
3679
  context.listen('init', ({events})=> {
3473
3680
  const e = tagBindMap[name.toLowerCase()];
@@ -3646,7 +3853,7 @@ function toText(val) {
3646
3853
  * @param {Element} parent
3647
3854
  * @param {Node?} next
3648
3855
  * @param {Environment} envs
3649
- * @param {Layout.Directives} layout
3856
+ * @param {Layout.Node} layout
3650
3857
  */
3651
3858
  function renderFillDirectives(parent, next, envs, { text, html }) {
3652
3859
  if (text != null) {
@@ -3700,18 +3907,18 @@ function renderList(layouts, parent, next, envs, templates, renderItem) {
3700
3907
 
3701
3908
  /** @type {Set<() => void>?} */
3702
3909
  let bkList = new Set();
3703
- /** @type {[string | Layout.Calc | null, Layout.Node][]} */
3910
+ /** @type {[Layout.Node.Name | Layout.Node.Calc | Layout.Node.Value | null, Layout.Node][]} */
3704
3911
  let ifList = [];
3705
3912
  /** @type {Record<string, [Layout.Node, Environment]>} */
3706
3913
  let currentTemplates = Object.create(templates);
3707
3914
  for (const layout of layouts) {
3708
3915
  if (typeof layout === 'string') { continue; }
3709
- const name = layout.directives.template;
3916
+ const name = layout.template;
3710
3917
  if (!name) { continue; }
3711
3918
  currentTemplates[name] = [layout, envs];
3712
3919
  }
3713
3920
 
3714
- /** @param {[string | Layout.Calc | null, Layout.Node][]} list */
3921
+ /** @param {[Layout.Node.Name | Layout.Node.Calc | Layout.Node.Value | null, Layout.Node][]} list */
3715
3922
  function renderIf(list) {
3716
3923
  if (!list.length || !bkList) { return; }
3717
3924
  const end = parent.insertBefore(document.createComment(''), next);
@@ -3735,7 +3942,7 @@ function renderList(layouts, parent, next, envs, templates, renderItem) {
3735
3942
  end.remove();
3736
3943
  });
3737
3944
  bkList.add(watch(
3738
- () => list.findIndex(([ifv]) => ifv === null || envs.exec(ifv)),
3945
+ () => list.findIndex(([ifv]) => !ifv || envs.exec(ifv)),
3739
3946
  index => {
3740
3947
  if (index === lastIndex) { return; }
3741
3948
  lastIndex = index;
@@ -3754,13 +3961,13 @@ function renderList(layouts, parent, next, envs, templates, renderItem) {
3754
3961
  bkList.add(() => node.remove());
3755
3962
  continue;
3756
3963
  }
3757
- if (layout.directives.template) {
3964
+ if (layout.template) {
3758
3965
  renderIf(ifList);
3759
3966
  ifList = [];
3760
3967
  continue;
3761
3968
  }
3762
- if (ifList.length && layout.directives.else) {
3763
- const ifv = layout.directives.if || null;
3969
+ if (ifList.length && layout.else) {
3970
+ const ifv = layout.if || null;
3764
3971
  ifList.push([ifv, layout]);
3765
3972
  if (!ifv) {
3766
3973
  renderIf(ifList);
@@ -3770,7 +3977,7 @@ function renderList(layouts, parent, next, envs, templates, renderItem) {
3770
3977
  }
3771
3978
  renderIf(ifList);
3772
3979
  ifList = [];
3773
- const ifv = layout.directives.if;
3980
+ const ifv = layout.if;
3774
3981
  if (ifv) {
3775
3982
  ifList.push([ifv, layout]);
3776
3983
  continue;
@@ -3779,6 +3986,7 @@ function renderList(layouts, parent, next, envs, templates, renderItem) {
3779
3986
  renderItem(layout, currentTemplates)
3780
3987
  );
3781
3988
  }
3989
+ renderIf(ifList);
3782
3990
 
3783
3991
  return () => {
3784
3992
  if (!bkList) { return; }
@@ -3925,6 +4133,135 @@ function bindBase(handler, env, bind) {
3925
4133
  }
3926
4134
  }
3927
4135
 
4136
+ /** @import Environment from './Environment/index.mjs' */
4137
+ /** @import { Enhancement } from '../types.mjs' */
4138
+ /** @import * as Layout from '../Layout/index.mjs' */
4139
+
4140
+ /**
4141
+ *
4142
+ * @param {object} obj
4143
+ * @param {string} name
4144
+ * @returns
4145
+ */
4146
+ const hasOwnProperty = (obj, name) => Object.prototype.hasOwnProperty.call(obj, name);
4147
+ /**
4148
+ * @param {any} tag
4149
+ * @param {Record<string, Layout.Enhancement>} enhancementDefine
4150
+ * @param {Environment} env
4151
+ * @param {Record<string, Enhancement>} enhancements
4152
+ * @param {Element} root
4153
+ * @param {Element?} [slot]
4154
+ */
4155
+ function bindEnhancements(tag, enhancementDefine, env, enhancements, root, slot) {
4156
+
4157
+ let bk = new Set();
4158
+ for (const [name, { attrs: attrDefine, value, events: eventsDefine, bind }] of Object.entries(enhancementDefine)) {
4159
+ if (!hasOwnProperty(enhancements, name)) { continue; }
4160
+ const enhancement = enhancements[name];
4161
+ if (typeof enhancement !== 'function') { continue; }
4162
+
4163
+
4164
+ let destroyed = false;
4165
+ const destroyedState = new Signal.State(false);
4166
+ const events = /** @type {any} */(enhancement)?.events;
4167
+ /** @type {[string, ($event: any) => void, AddEventListenerOptions][]} */
4168
+ const allEvents = [];
4169
+ const attrs = Object.create(null);
4170
+ /** @type {Set<() => void>} */
4171
+ const cancelFns = new Set();
4172
+ const stateEmitter = new EventEmitter();
4173
+
4174
+ /**
4175
+ *
4176
+ * @param {string} name
4177
+ * @param {Layout.EventListener} fn
4178
+ * @returns
4179
+ */
4180
+ function addEvent(name, fn) {
4181
+ if (typeof fn !== 'function') { return; }
4182
+ const [e, ...fs] = name.split('.').filter(Boolean);
4183
+ const filters = events ? events[e].filters : {};
4184
+ if (!filters) { return; }
4185
+ /** @type {AddEventListenerOptions} */
4186
+ const options = {};
4187
+ const filterFns = findFilters(fs, filters, options);
4188
+ allEvents.push([e, bindFilters(env, fn, filterFns), options]);
4189
+ }
4190
+ function destroy() {
4191
+ if (destroyed) { return; }
4192
+ destroyed = true;
4193
+ destroyedState.set(true);
4194
+ for (const w of cancelFns) {
4195
+ w();
4196
+ }
4197
+ stateEmitter.emit('destroy');
4198
+ }
4199
+ bk.add(destroy);
4200
+
4201
+
4202
+
4203
+ for (const [name, attr] of Object.entries(attrDefine)) {
4204
+ const s = env.get(attr);
4205
+ if (!s) { continue; }
4206
+ Object.defineProperty(attrs, name, { ...s, configurable: true, enumerable: true });
4207
+ }
4208
+
4209
+ for (const [name, event] of Object.entries(eventsDefine)) {
4210
+ const fn = env.getEvent(event);
4211
+ if (fn) { addEvent(name, fn); }
4212
+ }
4213
+
4214
+ if (bind) {
4215
+ for (const [key, effect] of Object.entries(env.getBindAll(bind) || {})) {
4216
+ Object.defineProperty(attrs, key, { ...effect, configurable: true, enumerable: true });
4217
+ }
4218
+ for (const [key, setter] of Object.entries(env.bindEvents(bind) || {})) {
4219
+ addEvent(key, $event => setter($event));
4220
+ }
4221
+
4222
+ }
4223
+ /**@type {Enhancement.Context} */
4224
+ const context = {
4225
+ get value() { return null; },
4226
+ events: allEvents,
4227
+ attrs: attrs,
4228
+ watchAttr(name, fn) {
4229
+ if (destroyed) { return () => { }; }
4230
+ let old = attrs[name];
4231
+ const w = watch(() => attrs[name], v => {
4232
+ const o = old;
4233
+ old = v;
4234
+ fn(v, o, name);
4235
+ }, true);
4236
+ cancelFns.add(w);
4237
+
4238
+ return () => {
4239
+ cancelFns.delete(w);
4240
+ w();
4241
+ };
4242
+ },
4243
+ get destroyed() { return destroyedState.get(); },
4244
+ listen(name, listener) { return stateEmitter.listen(name, listener); },
4245
+ root, slot,
4246
+ tag,
4247
+ };
4248
+ if (value) {
4249
+ const s = env.get(value);
4250
+ if (s) {
4251
+ Object.defineProperty(context, 'value', { ...s, configurable: true, enumerable: true });
4252
+ }
4253
+ }
4254
+ enhancement(context);
4255
+ }
4256
+ return () => {
4257
+ const list = bk;
4258
+ bk = new Set();
4259
+ for (const s of list) {
4260
+ s();
4261
+ }
4262
+ };
4263
+ }
4264
+
3928
4265
  /** @import Store from '../Store/index.mjs' */
3929
4266
 
3930
4267
  /**
@@ -3934,31 +4271,36 @@ function bindBase(handler, env, bind) {
3934
4271
  * @param {Environment} env
3935
4272
  * @param {Record<string, [Layout.Node, Environment]>} templates
3936
4273
  * @param {string[]} componentPath
3937
- * @param {((store: Store, el: Element) => () => void)?} [relate]
4274
+ * @param {Record<string, Enhancement>} enhancements
4275
+ * @param {((store: Store, el: Element | Relatedness) => () => void)?} [relate]
3938
4276
  * @param {Component.Getter?} [getComponent]
3939
4277
  */
3940
- function renderItem(layout, parent, next, env, templates, componentPath, relate, getComponent) {
4278
+ function renderItem(layout, parent, next, env, templates, componentPath, enhancements, relate, getComponent) {
3941
4279
  env = env.set(layout.aliases, layout.vars);
3942
- const bind = layout.directives.bind;
3943
- const fragment = layout.directives.fragment;
4280
+ const bind = layout.bind;
4281
+ const fragment = layout.fragment;
3944
4282
  if (fragment && typeof fragment === 'string') {
3945
4283
  const template = templates[fragment];
3946
4284
  if (!template) { return () => {}; }
3947
4285
  const [templateLayout, templateEnv] = template;
3948
4286
  const newEnv = templateEnv.params(templateLayout, layout, env, bind);
3949
- return render(templateLayout, parent, next, newEnv, templates, componentPath, relate, getComponent);
4287
+ return render(templateLayout, parent, next, newEnv, templates, componentPath, enhancements, relate, getComponent);
3950
4288
  }
3951
- if (!layout.name || layout.directives.fragment) {
3952
- return renderFillDirectives(parent, next, env, layout.directives) ||
4289
+ if (!layout.name || layout.fragment) {
4290
+ return renderFillDirectives(parent, next, env, layout) ||
3953
4291
  renderList(layout.children || [], parent, next, env, templates, (layout, templates) => {
3954
- return render(layout, parent, next, env, templates, componentPath, relate, getComponent);
4292
+ return render(layout, parent, next, env, templates, componentPath, enhancements, relate, getComponent);
3955
4293
  });
3956
4294
  }
3957
4295
  const path = [...componentPath, layout.name];
3958
4296
  const component = getComponent?.(path);
3959
4297
  if (getComponent && !component) { return () => { }; }
3960
- const { context, handler } = createContext(component ? component : layout.name, env, relate);
3961
-
4298
+ const { context, handler } = createContext(
4299
+ component ? component : layout.name,
4300
+ env,
4301
+ env.getStore(bind),
4302
+ relate
4303
+ );
3962
4304
 
3963
4305
  const componentAttrs = component?.attrs;
3964
4306
  const attrs = componentAttrs
@@ -3982,23 +4324,27 @@ function renderItem(layout, parent, next, env, templates, componentPath, relate,
3982
4324
  const slot = Array.isArray(r) ? r[1] : root;
3983
4325
  parent.insertBefore(root, next);
3984
4326
  const children = slot ?
3985
- renderFillDirectives(slot, null, env, layout.directives)
4327
+ renderFillDirectives(slot, null, env, layout)
3986
4328
  || renderList(layout.children || [], slot, null, env, templates, (layout, templates) => {
3987
- return render(layout, slot, null, env, templates, componentPath, relate, getComponent);
4329
+ return render(layout, slot, null, env, templates, componentPath, enhancements, relate, getComponent);
3988
4330
  }) : () => {};
3989
4331
 
3990
4332
 
3991
- bindClasses(root, layout.classes, env);
3992
- bindStyles(root, layout.styles, env);
4333
+ const classes = bindClasses(root, env, layout.classes, layout.attrs.class);
4334
+ const styles = bindStyles(root, env, layout.styles, layout.attrs.style);
3993
4335
 
3994
4336
  handler.mount();
4337
+ const enhancement = bindEnhancements(handler.tag, layout.enhancements, env, enhancements, root, slot);
3995
4338
 
3996
4339
  return () => {
3997
- root.remove();
4340
+ enhancement();
3998
4341
  handler.destroy();
4342
+ root.remove();
3999
4343
  attrs();
4000
4344
  children();
4001
4345
  base();
4346
+ classes();
4347
+ styles();
4002
4348
  };
4003
4349
  }
4004
4350
  /**
@@ -4009,17 +4355,17 @@ function renderItem(layout, parent, next, env, templates, componentPath, relate,
4009
4355
  * @param {Environment} env
4010
4356
  * @param {Record<string, [Layout.Node, Environment]>} templates
4011
4357
  * @param {string[]} componentPath
4012
- * @param {((store: Store, el: Element) => () => void)?} [relate]
4358
+ * @param {Record<string, Enhancement>} enhancements
4359
+ * @param {((store: Store, el: Element | Relatedness) => () => void)?} [relate]
4013
4360
  * @param {Component.Getter?} [getComponent]
4014
4361
  * @returns {() => void}
4015
4362
  */
4016
- function render(layout, parent, next, env, templates, componentPath, relate, getComponent) {
4017
- const { directives } = layout;
4018
- const newEnv = env.child(directives.value);
4363
+ function render(layout, parent, next, env, templates, componentPath, enhancements, relate, getComponent) {
4364
+ const newEnv = env.child(layout.value);
4019
4365
  if (!newEnv) { return () => {}; }
4020
- const list = newEnv.enum(directives.enum);
4366
+ const list = newEnv.enum(layout.enum);
4021
4367
  /** @type {(next: Node | null, env: any) => () => void} */
4022
- const r = (next, env) => renderItem(layout, parent, next, env, templates, componentPath, relate, getComponent);
4368
+ const r = (next, env) => renderItem(layout, parent, next, env, templates, componentPath, enhancements, relate, getComponent);
4023
4369
  if (list === true) {
4024
4370
  return r(next, newEnv);
4025
4371
  }
@@ -4042,15 +4388,17 @@ function render(layout, parent, next, env, templates, componentPath, relate, get
4042
4388
  * @param {object} [options]
4043
4389
  * @param {Record<string, Store | {get?(): any; set?(v: any): void; exec?(...p: any[]): any; calc?(...p: any[]): any }>} [options.global]
4044
4390
  * @param {(path: string[]) => Component?} [options.component]
4045
- * @param {(store: Store, el: Element) => () => void} [options.relate]
4391
+ * @param {(store: Store, el: Element | Relatedness) => () => void} [options.relate]
4392
+ * @param {Record<string, Enhancement>} [options.enhancements]
4046
4393
  * @returns {() => void}
4047
4394
  */
4048
- function index (store, layouts, parent, {component, global, relate} = {}) {
4395
+ function index (store, layouts, parent, {component, global, relate, enhancements} = {}) {
4049
4396
  const env = new Environment(store, global);
4050
4397
  const templates = Object.create(null);
4398
+ const allEnhancements = enhancements || {};
4051
4399
  const relateFn = typeof relate === 'function' ? relate : null;
4052
4400
  return renderList(layouts, parent, null, env, templates, (layout, templates) => {
4053
- return render(layout, parent, null, env, templates, [], relateFn, component);
4401
+ return render(layout, parent, null, env, templates, [], allEnhancements, relateFn, component);
4054
4402
  });
4055
4403
  }
4056
4404