@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.js 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
  */
@@ -651,7 +651,7 @@
651
651
 
652
652
  }
653
653
  /** @param {*} v */
654
- const values = v => {
654
+ const values$1 = v => {
655
655
  if (!v || !Array.isArray(v)) { return null;}
656
656
  const list = v.map(toValueItem).filter(valueFilter);
657
657
  if (!list.length) { return null; }
@@ -965,7 +965,7 @@
965
965
  validator, validators,
966
966
  index, length, new: isNew, parent: parentNode,
967
967
  hidden, clearable, required, disabled, readonly,
968
- label, description, placeholder, min, max, step, minLength, maxLength, pattern, values: values$1
968
+ label, description, placeholder, min, max, step, minLength, maxLength, pattern, values
969
969
  } = {}) {
970
970
  this.schema = schema;
971
971
  this.#state.set(typeof state === 'object' && state || {});
@@ -1028,7 +1028,7 @@
1028
1028
  [this.#selfMaxLength, this.#maxLength] = createState(this, number, maxLength, schema.maxLength);
1029
1029
  [this.#selfPattern, this.#pattern] = createState(this, regex, pattern, schema.pattern);
1030
1030
  // @ts-ignore
1031
- [this.#selfValues, this.#values] = createState(this, values, values$1, schema.values);
1031
+ [this.#selfValues, this.#values] = createState(this, values$1, values, schema.values);
1032
1032
 
1033
1033
  const validatorResult = createValidator(this, schema.validator, validator);
1034
1034
 
@@ -1260,9 +1260,9 @@
1260
1260
  /** @readonly @type {Signal.Computed<(Schema.Value.Group | Schema.Value)[] | null>} */
1261
1261
  #values
1262
1262
  get selfValues() { return this.#selfValues.get(); }
1263
- set selfValues(v) { this.#selfValues.set(values(v)); }
1263
+ set selfValues(v) { this.#selfValues.set(values$1(v)); }
1264
1264
  get values() { return this.#values.get(); }
1265
- set values(v) { this.#selfValues.set(values(v)); }
1265
+ set values(v) { this.#selfValues.set(values$1(v)); }
1266
1266
 
1267
1267
 
1268
1268
  /** @type {Signal.Computed<string[]>} */
@@ -1762,6 +1762,16 @@
1762
1762
  // @ts-ignore
1763
1763
  setArrayStore(ArrayStore);
1764
1764
 
1765
+ 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;
1766
+ /**
1767
+ *
1768
+ * @param {string} t
1769
+ * @returns
1770
+ */
1771
+ function parseNumber(t) {
1772
+ return numRegex.test(t) ? Number(t.replaceAll('_', '')) : NaN;
1773
+ }
1774
+
1765
1775
  const errors = {
1766
1776
  CALC: 'no `createCalc` option, no expression parsing support',
1767
1777
  EVENT: 'no `createEvent`, options, no event parsing support',
@@ -1809,64 +1819,137 @@
1809
1819
  /** @import * as Layout from './index.mjs' */
1810
1820
 
1811
1821
  const attrPattern = /^(?<decorator>[:@!+*\.?]|style:|样式:)?(?<name>-?[\w\p{Unified_Ideograph}_][-\w\p{Unified_Ideograph}_:\d\.]*)$/u;
1822
+ const enhancementPattern = /^~(?<enhancement>[\w\p{Unified_Ideograph}_][-\w\p{Unified_Ideograph}_\d\.]*)(?:(?<decorator>[:@!])(?<name>-?[\w\p{Unified_Ideograph}_][-\w\p{Unified_Ideograph}_:\d\.]*))?$/u;
1812
1823
  const nameRegex = /^(?<name>[a-zA-Z$\p{Unified_Ideograph}_][\da-zA-Z$\p{Unified_Ideograph}_]*)?$/u;
1824
+
1825
+ /**
1826
+ *
1827
+ * @param {Record<string, Layout.Enhancement>} enhancements
1828
+ * @param {string} name
1829
+ */
1830
+ function getEnhancement(enhancements, name) {
1831
+ const enhancement = enhancements[name];
1832
+ if (enhancement) { return enhancement; }
1833
+ /** @type {Layout.Enhancement} */
1834
+ const e = {
1835
+ attrs: Object.create(null),
1836
+ events: Object.create(null),
1837
+ };
1838
+ enhancements[name] = e;
1839
+ return e;
1840
+ }
1841
+ /**
1842
+ *
1843
+ * @param {string} value
1844
+ * @param {Exclude<Layout.Options['createCalc'], undefined>} createCalc
1845
+ * @returns {Layout.Node.Name | Layout.Node.Calc | Layout.Node.Value}
1846
+ */
1847
+ function parse$1(value, createCalc) {
1848
+ const text = value.replace(/$\s+|\s+$/gs,'');
1849
+ if (text === 'null') { return {value: null} }
1850
+ if (text === 'true') { return {value: true} }
1851
+ if (text === 'false') { return {value: false} }
1852
+ const t = parseNumber(text);
1853
+ if (!Number.isNaN(t)) { return {value: t}; }
1854
+ if (nameRegex.test(text)) { return {name: text} }
1855
+ return {calc: createCalc(value)};
1856
+ }
1857
+
1813
1858
  /**
1814
1859
  * @param {Layout.Node} node
1815
1860
  * @param {Exclude<Layout.Options['createCalc'], undefined>} createCalc
1816
1861
  * @param {Exclude<Layout.Options['createEvent'], undefined>} createEvent
1817
1862
  */
1818
1863
  function createAttributeAdder(node, createCalc, createEvent) {
1819
- const { attrs, directives, events, classes, styles, vars, aliases, params } = node;
1864
+ const { attrs, events, classes, styles, vars, aliases, params, enhancements } = node;
1820
1865
  /**
1821
1866
  * @param {string} qName
1822
1867
  * @param {string} value
1823
1868
  */
1824
1869
  function addAttribute(qName, value) {
1825
- const attr = attrPattern.exec(qName
1826
- .replace(/./g,'.')
1827
- .replace(/:/g,':')
1828
- .replace(/@/g,'@')
1829
- .replace(/+/g,'+')
1830
- .replace(/-/g,'-')
1831
- .replace(/[*×]/g,'*')
1832
- .replace(/!/g, '!'))?.groups;
1870
+ const qn = qName
1871
+ .replace(/./g,'.')
1872
+ .replace(/:/g,':')
1873
+ .replace(/@/g,'@')
1874
+ .replace(/+/g,'+')
1875
+ .replace(/-/g,'-')
1876
+ .replace(/[*×]/g,'*')
1877
+ .replace(/!/g, '!');
1878
+ const attr = (attrPattern.exec(qn) || enhancementPattern.exec(qn))?.groups;
1833
1879
  if (!attr) { throw new ParseError('ATTR', qName); }
1834
- const { name } = attr;
1880
+ const { name, enhancement } = attr;
1835
1881
  const decorator = attr.decorator?.toLowerCase();
1836
- if (!decorator) {
1837
- attrs[name] = value;
1838
- } else if (decorator === ':') {
1839
- attrs[name] = nameRegex.test(value) ? {name: value} : createCalc(value);
1840
- } else if (decorator === '.') {
1841
- classes[name] = !value ? true : nameRegex.test(value) ? value : createCalc(value);
1842
- } else if (decorator === 'style:') {
1843
- styles[name] = nameRegex.test(value) ? value : createCalc(value);
1844
- } else if (decorator === '@') {
1845
- events[name] = nameRegex.test(value) ? value : createEvent(value);
1846
- } else if (decorator === '+') {
1847
- vars[name] = !value ? '' : nameRegex.test(value) ? value : createCalc(value);
1848
- } else if (decorator === '*') {
1849
- aliases[name] = nameRegex.test(value) ? value : createCalc(value);
1850
- } else if (decorator === '?') {
1851
- params[name] = nameRegex.test(value) ? value : createCalc(value);
1852
- } else if (decorator === '!') {
1853
- const key = name.toString();
1854
- switch (key) {
1855
- case 'fragment': directives.fragment = value || true; break;
1856
- case 'else': directives.else = true; break;
1857
- case 'enum':
1858
- directives.enum = value ? nameRegex.test(value) ? value : createCalc(value) : true;
1859
- break;
1860
- case 'if':
1861
- case 'text':
1862
- case 'html':
1863
- directives[key] = nameRegex.test(value) ? value : createCalc(value);
1864
- break;
1865
- case 'template': directives.template = value; break;
1866
- case 'bind': directives.bind = value || true; break;
1867
- case 'value': directives.value = value; break;
1868
- case 'comment': directives.comment = value; break;
1882
+ if (enhancement) {
1883
+ if (decorator === ':') {
1884
+ getEnhancement(enhancements, enhancement).attrs[name] = value ? parse$1(value, createCalc) : {name};
1885
+ } else if (decorator === '@') {
1886
+ getEnhancement(enhancements, enhancement).events[name] = value ? nameRegex.test(value) ? {name: value} : {event: createEvent(value)} : {name};
1887
+ } else if (decorator === '!') {
1888
+ switch(name) {
1889
+ case 'bind':
1890
+ getEnhancement(enhancements, enhancement).bind = value || true;
1891
+ break;
1892
+ }
1893
+ } else {
1894
+ getEnhancement(enhancements, enhancement).value = parse$1(value, createCalc);
1869
1895
  }
1896
+ return;
1897
+ }
1898
+ if (!decorator) {
1899
+ attrs[name] = {value};
1900
+ return;
1901
+ }
1902
+ if (decorator === ':') {
1903
+ attrs[name] = value ? parse$1(value, createCalc) : {name};
1904
+ return;
1905
+ }
1906
+ if (decorator === '.') {
1907
+ classes[name] = value ? parse$1(value, createCalc) : {name};
1908
+ return;
1909
+ }
1910
+ if (decorator === 'style:') {
1911
+ styles[name] = parse$1(value, createCalc);
1912
+ return;
1913
+ }
1914
+ if (decorator === '@') {
1915
+ events[name] = value ? nameRegex.test(value) ? {name: value} : {event: createEvent(value)} : {name};
1916
+ return;
1917
+ }
1918
+ if (decorator === '+') {
1919
+ vars[name] = !value ? {value: undefined} : parse$1(value, createCalc);
1920
+ return;
1921
+ }
1922
+ if (decorator === '*') {
1923
+ aliases[name] = parse$1(value, createCalc);
1924
+ return;
1925
+ }
1926
+ if (decorator === '?') {
1927
+ params[name] = parse$1(value, createCalc);
1928
+ return;
1929
+ }
1930
+ if (decorator !== '!') {
1931
+ return;
1932
+ }
1933
+ const key = name.toString();
1934
+ switch (key) {
1935
+ case 'fragment': node.fragment = value || true; break;
1936
+ case 'else': node.else = true; break;
1937
+ case 'enum':
1938
+ node.enum = value ? parse$1(value, createCalc) : {value: true};
1939
+ break;
1940
+ case 'if':
1941
+ node.if = parse$1(value, createCalc);
1942
+ break;
1943
+ case 'text':
1944
+ node.text = parse$1(value, createCalc);
1945
+ break;
1946
+ case 'html':
1947
+ node.html = parse$1(value, createCalc);
1948
+ break;
1949
+ case 'template': node.template = value; break;
1950
+ case 'bind': node.bind = value || true; break;
1951
+ case 'value': node.value = value; break;
1952
+ case 'comment': node.comment = value; break;
1870
1953
  }
1871
1954
  }
1872
1955
  return addAttribute;
@@ -1887,12 +1970,12 @@
1887
1970
  children: [],
1888
1971
  attrs: Object.create(null),
1889
1972
  events: Object.create(null),
1890
- directives: Object.create(null),
1891
1973
  classes: Object.create(null),
1892
1974
  styles: Object.create(null),
1893
1975
  vars: Object.create(null),
1894
1976
  aliases: Object.create(null),
1895
1977
  params: Object.create(null),
1978
+ enhancements: Object.create(null),
1896
1979
  };
1897
1980
  }
1898
1981
 
@@ -2404,6 +2487,54 @@
2404
2487
  '&#' + c.charCodeAt() + ';';
2405
2488
  }
2406
2489
 
2490
+ /**
2491
+ * @param {Layout.Node.Name | Layout.Node.Calc | Layout.Node.Event | Layout.Node.Value} def
2492
+ */
2493
+ function toValue({name, calc, event, value}) {
2494
+ if (value === true || value === undefined) { return ''; }
2495
+ const val = typeof value === 'string' ? JSON.stringify(value) : name || calc || event || value;
2496
+ return `="${toAttrValue$1(val)}"`;
2497
+ }
2498
+ /**
2499
+ * @param {string | Function | null} [value]
2500
+ */
2501
+ function toAttrValue$1(value) {
2502
+ return String(value).replace(/[<&"]/g, _xmlEncoder);
2503
+ }
2504
+
2505
+ /**
2506
+ * @param {Record<string, Layout.Node.Name | Layout.Node.Calc | Layout.Node.Event | Layout.Node.Value>} values
2507
+ * @param {string} [prefix]
2508
+ * @param {boolean | null} [isName]
2509
+ */
2510
+ function *values(values, prefix, isName = false) {
2511
+ if (prefix) {
2512
+ for (const [key, {name, calc, event, value}] of Object.entries(values)) {
2513
+ yield ` ${prefix}${key}`;
2514
+ if (isName && name === key) { continue; }
2515
+ const val = value && typeof value === 'string' ? JSON.stringify(value) : name || calc || event || value;
2516
+ if (val == null) { continue; }
2517
+ if (isName === false && val === true) { continue; }
2518
+ yield `="${toAttrValue$1(val)}"`;
2519
+ }
2520
+ return;
2521
+ }
2522
+ for (const [key, attr] of Object.entries(values)) {
2523
+ if (!attr) { continue; }
2524
+ const {name, value, calc} = attr;
2525
+ if (name === key) { yield ` :${key}`; continue; }
2526
+ if (name || calc || typeof value !== 'string') {
2527
+ yield ` :${key}="${toAttrValue$1(name || calc || value)}"`;
2528
+ continue;
2529
+ }
2530
+ yield ` ${key}`;
2531
+ if (value) {
2532
+ yield `="${toAttrValue$1(value)}"`;
2533
+ }
2534
+ }
2535
+ }
2536
+
2537
+
2407
2538
  /**
2408
2539
  *
2409
2540
  * @param {Layout.Node} node
@@ -2411,81 +2542,37 @@
2411
2542
  * @returns {Iterable<string>}
2412
2543
  */
2413
2544
  function* nodeToString(node, level = 0) {
2414
- const { attrs, events, directives, children, is, name, params, classes, styles, aliases, vars } = node;
2545
+ const { children, is, name } = node;
2415
2546
  const pad = level > 0 ? ''.padEnd(level, '\t') : '';
2416
2547
 
2417
2548
  yield pad;
2418
2549
  yield* ['<', name || '-'];
2419
2550
  if (is) { yield* ['|', is]; }
2420
-
2421
- for (const [name, value] of Object.entries(params)) {
2422
- if (value == null) { continue; }
2423
- const val = typeof value === 'function' ? String(value) : value;
2424
- yield* [' ?', name];
2425
- if (val && typeof val === 'string') {
2426
- yield* ['="', val.replace(/[<&"]/g, _xmlEncoder), '"'];
2427
- }
2428
- }
2429
- for (const [name, value] of Object.entries(directives)) {
2430
- if (value === false || value == null) { continue; }
2431
- const val = typeof value === 'function' ? String(value) : value;
2432
- yield* [' !', name];
2433
- if (val && typeof val === 'string') {
2434
- yield* ['="', val.replace(/[<&"]/g, _xmlEncoder), '"'];
2435
- }
2436
- }
2437
- for (const [name, value] of Object.entries(aliases)) {
2438
- if (value == null) { continue; }
2439
- const val = typeof value === 'function' ? String(value) : value;
2440
- yield* [' *', name];
2441
- if (val && typeof val === 'string') {
2442
- yield* ['="', val.replace(/[<&"]/g, _xmlEncoder), '"'];
2443
- }
2444
- }
2445
- for (const [name, value] of Object.entries(vars)) {
2446
- if (value == null) { continue; }
2447
- const val = typeof value === 'function' ? String(value) : value;
2448
- yield* [' +', name];
2449
- if (val && typeof val === 'string') {
2450
- yield* ['="', val.replace(/[<&"]/g, _xmlEncoder), '"'];
2451
- }
2452
- }
2453
- for (const [name, value] of Object.entries(attrs)) {
2454
- if (value == null) { continue; }
2455
- if (typeof value === 'string') {
2456
- yield* [' ', name];
2457
- if (value) {
2458
- yield* ['="', value.replace(/[<&"]/g, _xmlEncoder), '"'];
2459
- }
2460
- continue;
2461
- }
2462
- const val = typeof value === 'function' ? String(value) : typeof value === 'object' ? value.name : value;
2463
- if (val && typeof val === 'string') {
2464
- yield* [' :', name, '="', val.replace(/[<&"]/g, _xmlEncoder) || '', '"'];
2465
- }
2551
+ if (node.template) {
2552
+ yield ` !template="${toAttrValue$1(node.template)}"`;
2553
+ yield* values(node.params, '?', null);
2466
2554
  }
2467
- for (const [name, value] of Object.entries(events)) {
2468
- if (value == null) { continue; }
2469
- const val = typeof value === 'function' ? String(value) : value;
2470
- if (val && typeof val === 'string') {
2471
- yield* [' @', name, '="', val.replace(/[<&"]/g, _xmlEncoder), '"'];
2472
- }
2473
- }
2474
- for (const [name, value] of Object.entries(classes)) {
2475
- if (value == null || value == false) { continue; }
2476
- const val = typeof value === 'function' ? String(value) : value;
2477
- yield* [' .', name];
2478
- if (val && typeof val === 'string') {
2479
- yield* [' .', name, '="', val.replace(/[<&"]/g, _xmlEncoder), '"'];
2480
- }
2481
- }
2482
- for (const [name, value] of Object.entries(styles)) {
2483
- if (value == null) { continue; }
2484
- const val = typeof value === 'function' ? String(value) : value;
2485
- if (val && typeof val === 'string') {
2486
- yield* [' style:', name, '="', val.replace(/[<&"]/g, _xmlEncoder), '"'];
2487
- }
2555
+ if (node.fragment) { yield node.fragment === true ? ` !fragment` : ` !fragment="${toAttrValue$1(node.fragment)}"`; }
2556
+ if (node.else) { yield ` !else`; }
2557
+ if (node.if) { yield ` !if${toValue(node.if)}`; }
2558
+ if (node.value) { yield ` !value="${toAttrValue$1(node.value)}"`; }
2559
+ if (node.enum) { yield ` !enum${toValue(node.enum)}`; }
2560
+ yield* values(node.aliases, '*', null);
2561
+ yield* values(node.vars, '+', null);
2562
+ if (node.bind) { yield node.bind === true ? ` !bind` : ` !bind="${toAttrValue$1(node.bind)}"`; }
2563
+ yield* values(node.attrs);
2564
+ yield* values(node.events, '@', true);
2565
+ yield* values(node.classes, '.', true);
2566
+ yield* values(node.styles, 'style:');
2567
+ for (const [k, en] of Object.entries(node.enhancements)) {
2568
+ if (en.bind) { yield en.bind === true ? ` ~${k}!bind` : ` ~${k}!bind="${toAttrValue$1(en.bind)}"`; }
2569
+ if (en.value) { yield ` ~${k}${toValue(en.value)}`; }
2570
+ yield* values(en.attrs, `~${k}:`, true);
2571
+ yield* values(en.events, `~${k}@`, true);
2488
2572
  }
2573
+ if (node.text) { yield ` !text${toValue(node.text)}`; }
2574
+ if (node.html) { yield ` !html${toValue(node.html)}`; }
2575
+ if (node.comment) { yield ` !comment="${toAttrValue$1(node.comment)}"`; }
2489
2576
  if (!children.length) {
2490
2577
  yield '/>';
2491
2578
  if (level >= 0) { yield '\n'; }
@@ -2547,44 +2634,66 @@
2547
2634
  /**
2548
2635
  * @typedef {object} Directives
2549
2636
  *
2550
- * @property {string} [template]
2551
- *
2552
- * @property {boolean | string} [fragment]
2553
- *
2554
- * @property {string | Calc} [if]
2555
- * @property {boolean} [else]
2556
- *
2557
- * @property {string} [value] 值关联(关联为列表)
2558
- * @property {boolean | string | Calc} [enum] 列表属性枚举
2559
- *
2560
- * @property {boolean | string} [bind]
2561
- * @property {string | Calc} [text]
2562
- * @property {string | Calc} [html]
2563
- *
2564
- * @property {string} [comment] 注释
2565
2637
  */
2566
2638
 
2567
2639
 
2568
2640
 
2641
+ /**
2642
+ * @typedef {object} Enhancement
2643
+ * @property {Record<string, Node.Name | Node.Event>} events
2644
+ * @property {Record<string, Node.Name | Node.Calc | Node.Value>} attrs
2645
+ * @property {Node.Name | Node.Calc | Node.Value} [value]
2646
+ * @property {boolean | string} [bind]
2647
+ */
2648
+
2569
2649
  /**
2570
2650
  * @typedef {object} Options
2571
2651
  * @property {(t: string) => Calc} [options.createCalc]
2572
2652
  * @property {(t: string) => EventListener} [options.createEvent]
2573
2653
  * @property {Set<string>} [options.simpleTag]
2574
2654
  */
2655
+ /**
2656
+ * @template [T=any]
2657
+ * @typedef {{value: T; name?: undefined; calc?: undefined; event?: undefined}} Node.Value
2658
+ */
2659
+ /**
2660
+ * @typedef {{name: string; value?: undefined; calc?: undefined; event?: undefined}} Node.Name
2661
+ */
2662
+ /**
2663
+ * @typedef {{event: EventListener; name?: undefined; calc?: undefined; value?: undefined}} Node.Event
2664
+ */
2665
+ /**
2666
+ * @typedef {{calc: Calc; name?: undefined; value?: undefined; event?: undefined}} Node.Calc
2667
+ */
2575
2668
  /**
2576
2669
  * @typedef {object} Node
2577
2670
  * @property {string} name
2578
2671
  * @property {string?} [is]
2579
2672
  * @property {string} [id]
2580
- * @property {Record<string, string | {name: string} | Calc>} attrs
2581
- * @property {Record<string, string | Calc>} params
2582
- * @property {Record<string, string | boolean | Calc>} classes
2583
- * @property {Record<string, string | Calc>} styles
2584
- * @property {Record<string, string | EventListener>} events
2585
- * @property {Record<string, string | Calc>} vars
2586
- * @property {Record<string, string | Calc>} aliases
2587
- * @property {Directives} directives
2673
+ * @property {Record<string, Node.Name | Node.Calc | Node.Value>} attrs
2674
+ * @property {Record<string, Node.Name | Node.Calc | Node.Value>} params
2675
+ * @property {Record<string, Node.Name | Node.Calc | Node.Value>} classes
2676
+ * @property {Record<string, Node.Name | Node.Calc | Node.Value>} styles
2677
+ * @property {Record<string, Node.Name | Node.Event>} events
2678
+ * @property {Record<string, Node.Name | Node.Calc | Node.Value>} vars
2679
+ * @property {Record<string, Node.Name | Node.Calc | Node.Value>} aliases
2680
+ * @property {Record<string, Enhancement>} enhancements
2681
+ *
2682
+ * @property {string} [template]
2683
+ * @property {boolean | string} [fragment]
2684
+ *
2685
+ * @property {Node.Name | Node.Calc | Node.Value} [if]
2686
+ * @property {boolean} [else]
2687
+ *
2688
+ * @property {string} [value] 值关联
2689
+ * @property {Node.Name | Node.Calc | Node.Value} [enum] 列表属性枚举
2690
+ *
2691
+ * @property {boolean | string} [bind]
2692
+ * @property {Node.Name | Node.Value | Node.Calc} [text]
2693
+ * @property {Node.Name | Node.Value | Node.Calc} [html]
2694
+ *
2695
+ * @property {string} [comment] 注释
2696
+ *
2588
2697
  * @property {(Node | string)[]} children
2589
2698
  */
2590
2699
 
@@ -2592,7 +2701,7 @@
2592
2701
  * @callback Calc
2593
2702
  * @param {Record<string, any>} env
2594
2703
  * @returns {any}
2595
- */
2704
+ */
2596
2705
 
2597
2706
 
2598
2707
  /**
@@ -2889,37 +2998,63 @@
2889
2998
  */
2890
2999
  class Environment {
2891
3000
  /**
2892
- * @param {string | Layout.Calc} value
3001
+ * @param {Layout.Node.Name | Layout.Node.Calc | Layout.Node.Value} value
2893
3002
  */
2894
- exec(value) {
2895
- if (typeof value === 'string') {
2896
- const item = this.#items[value];
3003
+ exec({name, calc, value }) {
3004
+ if (typeof name === 'string') {
3005
+ const item = this.#items[name];
2897
3006
  if (typeof item?.get !== 'function') { return }
2898
3007
  return item.get();
2899
3008
  }
2900
- if (typeof value === 'function') {
2901
- return value(this.getters);
3009
+ if (typeof calc === 'function') {
3010
+ return calc(this.getters);
2902
3011
  }
3012
+ return value;
2903
3013
  }
2904
3014
  /**
2905
- * @param {string | Layout.Calc} value
3015
+ * @param {Layout.Node.Name | Layout.Node.Calc | Layout.Node.Value} value
3016
+ */
3017
+ get({name, calc, value}) {
3018
+ if (typeof name === 'string') {
3019
+ const item = this.#items[name];
3020
+ if (typeof item?.get !== 'function') { return }
3021
+ const{get, set} = item;
3022
+ return {get, set};
3023
+ }
3024
+ if (typeof calc === 'function') {
3025
+ const c = new exports.Signal.Computed(() => calc(this.getters));
3026
+ return {get: () => c.get() };
3027
+ }
3028
+ return {get: () => value };
3029
+ }
3030
+ /**
3031
+ * @param {Layout.Node.Name | Layout.Node.Calc | Layout.Node.Value} value
2906
3032
  * @param {(value: any) => void} cb
2907
3033
  */
2908
3034
  watch(value, cb) { return watch(() => this.exec(value), cb, true); }
2909
3035
 
2910
3036
  /**
2911
- * @param {string | Layout.Calc | boolean | null} [name]
3037
+ * @param {Layout.Node.Name | Layout.Node.Calc | Layout.Node.Value | null} [en]
2912
3038
  */
2913
- enum(name) {
2914
- if (!name) { return true; }
2915
- if (name === true) { return this.store; }
2916
- if (typeof name === 'function') { return () => name(this.getters); }
2917
- if (typeof name !== 'string') { return null; }
2918
- const item = this.#items[name];
2919
- if (typeof item?.get !== 'function') { return null }
2920
- const store = item.store;
2921
- return store instanceof Store ? store : item.get;
2922
-
3039
+ enum(en) {
3040
+ if (!en) { return true; }
3041
+ const {name, calc} = en;
3042
+ if (typeof calc === 'function') { return () => calc(this.getters); }
3043
+ if (typeof name === 'string') {
3044
+ const item = this.#items[name];
3045
+ if (typeof item?.get !== 'function') { return null }
3046
+ const store = item.store;
3047
+ return store instanceof Store ? store : item.get;
3048
+ }
3049
+ return this.store;
3050
+ }
3051
+ /**
3052
+ * @param {string | boolean | null} [name]
3053
+ */
3054
+ getStore(name) {
3055
+ if (!name) { return null; }
3056
+ const item = this.#items[name === true ? '' : name];
3057
+ return item?.get && item.store || null
2923
3058
  }
2924
3059
 
2925
3060
  /**
@@ -2962,6 +3097,29 @@
2962
3097
  ]));
2963
3098
  return res;
2964
3099
  }
3100
+ /**
3101
+ * @param {string | true} name
3102
+ * @returns {Record<string, {get(): any; set?(v: any): void}> | void}
3103
+ */
3104
+ getBindAll(name) {
3105
+ const item = this.#items[name === true ? '' : name];
3106
+ if (!item?.get) { return {}; }
3107
+ const { store } = item;
3108
+ if (!store) {
3109
+ const { get, set } = item;
3110
+ return { '$value': {get,set} }
3111
+ }
3112
+ /** @type {Record<string, {get(): any; set?(v: any): void}> | void} */
3113
+ const res = Object.fromEntries([...bindableSet].map(v => [
3114
+ `$${v}`, v === 'value' || v === 'state' ? {
3115
+ get: () => store[v],
3116
+ set: (s)=>{store[v] = s;}
3117
+ } : {
3118
+ get: () => store[v],
3119
+ }
3120
+ ]));
3121
+ return res;
3122
+ }
2965
3123
  /**
2966
3124
  * @param {string | true} name
2967
3125
  * @param {string} type
@@ -3005,12 +3163,12 @@
3005
3163
  }
3006
3164
 
3007
3165
  /**
3008
- * @param {string | Layout.EventListener} event
3166
+ * @param {Layout.Node.Name | Layout.Node.Event} event
3009
3167
  * @returns {Layout.EventListener?}
3010
3168
  */
3011
- getEvent(event) {
3169
+ getEvent({name, event}) {
3012
3170
  if (typeof event === 'function') { return event }
3013
- const item = this.#items[event];
3171
+ const item = this.#items[name];
3014
3172
  if (!item) { return null }
3015
3173
  const {exec, calc} = item;
3016
3174
  if (typeof exec === 'function') { return exec }
@@ -3128,45 +3286,55 @@
3128
3286
  const items = cloned.#items;
3129
3287
  for (const [key, param] of Object.entries(params)) {
3130
3288
  const attr = key in attrs ? attrs[key] : null;
3131
- if (typeof attr === 'string') {
3132
- explicit[key] = items[key] = {get: () => attr};
3133
- } else if (attr && typeof attr === 'object') {
3134
- const item = sourceEnv.#items[attr.name];
3135
- if (!item?.get) { continue; }
3136
- if (!item.store) {
3137
- explicit[key] = items[key] = item;
3289
+ if (attr) {
3290
+ const {name, calc, value} = attr;
3291
+ if (name) {
3292
+ const item = sourceEnv.#items[name];
3293
+ if (!item?.get) { continue; }
3294
+ if (!item.store) {
3295
+ explicit[key] = items[key] = item;
3296
+ continue;
3297
+ }
3298
+ for (const [k, it] of toItem(item.store, key)) {
3299
+ explicit[k] = items[k] = it;
3300
+ }
3138
3301
  continue;
3139
- }
3140
- for (const [k, it] of toItem(item.store, key)) {
3141
- explicit[k] = items[k] = it;
3302
+ } else if (typeof calc === 'function') {
3303
+ const val = new exports.Signal.Computed(() => calc(sourceEnv.getters));
3304
+ explicit[key] = items[key] = {
3305
+ get: () => { return val.get(); },
3306
+ };
3307
+ continue;
3308
+ } else {
3309
+ explicit[key] = items[key] = {get: () => value};
3142
3310
  }
3143
3311
  continue;
3144
-
3145
- } else if (typeof attr === 'function') {
3146
- const val = new exports.Signal.Computed(() => attr(sourceEnv.getters));
3147
- explicit[key] = items[key] = {
3148
- get: () => { return val.get(); },
3149
- };
3150
- continue;
3151
- } else if (typeof param === 'function') {
3152
- const getters = cloned.getters;
3153
- cloned.#getters = null;
3154
- const val = new exports.Signal.Computed(() => param(getters));
3155
- explicit[key] = items[key] = {
3156
- get: () => { return val.get(); },
3157
- };
3158
- continue;
3159
3312
  } else {
3160
- const item = items[param];
3161
- if (!item?.get) { continue; }
3162
- if (!item.store) {
3163
- explicit[key] = items[key] = item;
3313
+ const {name, calc, value} = param;
3314
+ if (typeof calc === 'function') {
3315
+ const getters = cloned.getters;
3316
+ cloned.#getters = null;
3317
+ const val = new exports.Signal.Computed(() => calc(getters));
3318
+ explicit[key] = items[key] = {
3319
+ get: () => { return val.get(); },
3320
+ };
3164
3321
  continue;
3322
+ } else if (name) {
3323
+ const item = items[name];
3324
+ if (!item?.get) { continue; }
3325
+ if (!item.store) {
3326
+ explicit[key] = items[key] = item;
3327
+ continue;
3328
+ }
3329
+ for (const [k, it] of toItem(item.store, key)) {
3330
+ explicit[k] = items[k] = it;
3331
+ }
3332
+ continue;
3333
+ } else {
3334
+ explicit[key] = items[key] = {get: () => value};
3335
+ continue;
3336
+
3165
3337
  }
3166
- for (const [k, it] of toItem(item.store, key)) {
3167
- explicit[k] = items[k] = it;
3168
- }
3169
- continue;
3170
3338
  }
3171
3339
  }
3172
3340
  return cloned;
@@ -3179,8 +3347,8 @@
3179
3347
  }
3180
3348
  /**
3181
3349
  *
3182
- * @param {Record<string, string | Layout.Calc>} aliases
3183
- * @param {Record<string, string | Layout.Calc>} vars
3350
+ * @param {Record<string, Layout.Node.Name | Layout.Node.Calc | Layout.Node.Value>} aliases
3351
+ * @param {Record<string, Layout.Node.Name | Layout.Node.Calc | Layout.Node.Value>} vars
3184
3352
  */
3185
3353
  set(aliases, vars) {
3186
3354
  if (Object.keys(aliases).length + Object.keys(vars).length === 0) { return this; }
@@ -3189,35 +3357,40 @@
3189
3357
  cloned.#object = this.#object;
3190
3358
  const explicit = cloned.#explicit;
3191
3359
  const items = cloned.#items;
3192
- for (const [key, name] of Object.entries(aliases)) {
3193
- if (typeof name === 'function') {
3360
+ for (const [key, {name, calc, value}] of Object.entries(aliases)) {
3361
+ if (typeof calc === 'function') {
3194
3362
  const getters = cloned.getters;
3195
3363
  cloned.#getters = null;
3196
- const val = new exports.Signal.Computed(() => name(getters));
3364
+ const val = new exports.Signal.Computed(() => calc(getters));
3197
3365
  explicit[key] = items[key] = {
3198
3366
  get: () => { return val.get(); },
3199
3367
  };
3200
3368
  continue;
3201
3369
  }
3202
- const item = items[name];
3203
- if (!item) { continue; }
3204
- if (!item.get || !item.store) {
3205
- explicit[key] = items[key] = item;
3370
+ if (name) {
3371
+ const item = items[name];
3372
+ if (!item) { continue; }
3373
+ if (!item.get || !item.store) {
3374
+ explicit[key] = items[key] = item;
3375
+ continue;
3376
+ }
3377
+ for (const [k, it] of toItem(item.store, key)) {
3378
+ explicit[k] = items[k] = it;
3379
+ }
3206
3380
  continue;
3207
3381
  }
3208
- for (const [k, it] of toItem(item.store, key)) {
3209
- explicit[k] = items[k] = it;
3210
- }
3382
+ explicit[key] = items[key] = { get: () => { return value; } };
3383
+ continue;
3211
3384
  }
3212
- for (const [k,v] of Object.entries(vars)) {
3385
+ for (const [k,{name, calc, value}] of Object.entries(vars)) {
3213
3386
 
3214
- const val = new exports.Signal.State(/** @type {any} */(null));
3215
- if (typeof v === 'function') {
3387
+ const val = new exports.Signal.State(/** @type {any} */(value));
3388
+ if (typeof calc === 'function') {
3216
3389
  const settable = cloned.settable;
3217
3390
  cloned.#settable = null;
3218
- val.set(v(settable));
3219
- } else if (v && typeof v === 'string') {
3220
- const item = items[v];
3391
+ val.set(calc(settable));
3392
+ } else if (name) {
3393
+ const item = items[name];
3221
3394
  if (!item?.get) { continue }
3222
3395
  val.set(item.get());
3223
3396
  }
@@ -3328,44 +3501,44 @@
3328
3501
  /**
3329
3502
  * @param {Component.Handler} handler
3330
3503
  * @param {Environment} envs
3331
- * @param {Record<string, string | {name: string} | Layout.Calc>} attrs
3504
+ * @param {Record<string, Layout.Node.Name | Layout.Node.Calc | Layout.Node.Value>} attrs
3332
3505
  * @param {Record<string, Component.Attr>} componentAttrs
3333
3506
  * @param {string | boolean | null} [bindValue]
3334
3507
  */
3335
3508
  function bindAttrs(handler, envs, attrs, componentAttrs, bindValue) {
3336
3509
 
3337
3510
  let bk = new Set();
3338
- for (const [name, attr] of Object.entries(componentAttrs)) {
3339
- const attrValue = attrs[name];
3340
- if (name in attrs) {
3341
- if (typeof attrValue !== 'function' && typeof attrValue !== 'object') {
3342
- handler.set(name, attrValue);
3511
+ for (const [key, attr] of Object.entries(componentAttrs)) {
3512
+ if (key === 'class' || key === 'style') { continue; }
3513
+ if (key in attrs) {
3514
+ const attrDefine = attrs[key];
3515
+ const { name, calc, value } = attrs[key];
3516
+ if (!name && !calc) {
3517
+ handler.set(key, value);
3343
3518
  continue;
3344
3519
  }
3345
- const attrSchema = typeof attrValue === 'function' ? /** @type{Layout.Calc} */(attrValue) : attrValue.name;
3346
3520
  if (attr.immutable) {
3347
- handler.set(name, envs.exec(attrSchema));
3348
- continue;
3521
+ handler.set(key, envs.exec(attrDefine));
3349
3522
  }
3350
- bk.add(envs.watch(attrSchema, v => handler.set(name, v)));
3523
+ bk.add(envs.watch(attrDefine, v => handler.set(key, v)));
3351
3524
  continue;
3352
3525
  }
3353
3526
  const bind = attr.bind;
3354
3527
  if (!bindValue || !bind) {
3355
- handler.set(name, attr.default);
3528
+ handler.set(key, attr.default);
3356
3529
  continue;
3357
3530
  }
3358
3531
  if (typeof bind === 'string') {
3359
- const r = envs.bind(bindValue, bind, v => handler.set(name, v));
3532
+ const r = envs.bind(bindValue, bind, v => handler.set(key, v));
3360
3533
  if (r) {
3361
3534
  bk.add(r);
3362
3535
  } else {
3363
- handler.set(name, attr.default);
3536
+ handler.set(key, attr.default);
3364
3537
  }
3365
3538
  continue;
3366
3539
  }
3367
3540
  if (!Array.isArray(bind)) {
3368
- handler.set(name, attr.default);
3541
+ handler.set(key, attr.default);
3369
3542
  continue;
3370
3543
  }
3371
3544
  const [event, set, isState] = bind;
@@ -3374,11 +3547,11 @@
3374
3547
  }
3375
3548
  if (!isState) {
3376
3549
  const bindKey = bindValue === true ? '' : bindValue;
3377
- bk.add(envs.watch(bindKey, v => handler.set(name, v)));
3550
+ bk.add(envs.watch({name: bindKey}, v => handler.set(key, v)));
3378
3551
  handler.addEvent(event, (...args) => { envs.all[bindKey] = set(...args);});
3379
3552
  continue;
3380
3553
  }
3381
- const r = envs.bind(bindValue, 'state', v => handler.set(name, v));
3554
+ const r = envs.bind(bindValue, 'state', v => handler.set(key, v));
3382
3555
  if (!r) { continue; }
3383
3556
  bk.add(r);
3384
3557
  const s = envs.bindSet(bindValue, 'state');
@@ -3402,23 +3575,24 @@
3402
3575
  /**
3403
3576
  * @param {Component.Handler} handler
3404
3577
  * @param {Environment} envs
3405
- * @param {Record<string, string | {name: string} | Layout.Calc>} attrs
3578
+ * @param {Record<string, Layout.Node.Name | Layout.Node.Calc | Layout.Node.Value>} attrs
3406
3579
  */
3407
3580
  function bindBaseAttrs(handler, envs, attrs) {
3408
3581
  const tag = handler.tag;
3409
3582
  let bk = new Set();
3410
- for (const [name, attr] of Object.entries(attrs)) {
3411
- if (typeof attr !== 'function' && typeof attr !== 'object') {
3412
- handler.set(name, attr);
3583
+ for (const [key, attr] of Object.entries(attrs)) {
3584
+ if (key === 'class' || key === 'style') { continue; }
3585
+ const {name, calc, value} = attr;
3586
+ if (!name && !calc) {
3587
+ handler.set(key, value);
3413
3588
  continue;
3414
3589
  }
3415
- const attrSchema = typeof attr === 'function' ? /** @type{Layout.Calc} */(attr) : attr.name;
3416
- if (typeof tag === 'string' && tag.toLocaleLowerCase() === 'input' && name.toLocaleLowerCase() === 'type') {
3417
- const value = envs.exec(attrSchema);
3418
- handler.set(name, value);
3590
+ if (typeof tag === 'string' && tag.toLocaleLowerCase() === 'input' && key.toLocaleLowerCase() === 'type') {
3591
+ const value = envs.exec(attr);
3592
+ handler.set(key, value);
3419
3593
  continue;
3420
3594
  }
3421
- bk.add(envs.watch(attrSchema, val => handler.set(name, val)));
3595
+ bk.add(envs.watch(attr, val => handler.set(key, val)));
3422
3596
  }
3423
3597
 
3424
3598
  return ()=> {
@@ -3436,26 +3610,29 @@
3436
3610
  /**
3437
3611
  * @param {Node} node
3438
3612
  * @param {Environment} envs
3439
- * @param {Record<string, string | boolean | Layout.Calc>} classes
3613
+ * @param {Record<string, Layout.Node.Name | Layout.Node.Calc | Layout.Node.Value>} classes
3614
+ * @param {Layout.Node.Name | Layout.Node.Calc | Layout.Node.Value} [classAttr]
3440
3615
  */
3441
- function bindClasses(node, classes, envs) {
3616
+ function bindClasses(node, envs, classes, classAttr) {
3442
3617
  if (!(node instanceof Element)) {
3443
3618
  return () => {};
3444
3619
  }
3620
+ if (classAttr) {
3621
+ node.className += ' ' + envs.exec(classAttr);
3622
+ }
3445
3623
 
3446
3624
  /** @type {Set<() => void>?} */
3447
3625
  let bk = new Set();
3448
- for (const [name, attr] of Object.entries(classes)) {
3449
- if (!attr) { continue; }
3450
- if (attr === true) {
3451
- node.classList.add(name);
3626
+ for (const [key, attr] of Object.entries(classes)) {
3627
+ if (attr.value) {
3628
+ node.classList.add(key);
3452
3629
  continue;
3453
3630
  }
3454
3631
  bk.add(watch(() => Boolean(envs.exec(attr)), value => {
3455
3632
  if (value) {
3456
- node.classList.add(name);
3633
+ node.classList.add(key);
3457
3634
  } else {
3458
- node.classList.remove(name);
3635
+ node.classList.remove(key);
3459
3636
  }
3460
3637
  }, true));
3461
3638
  }
@@ -3539,16 +3716,20 @@
3539
3716
  /**
3540
3717
  * @param {Element} node
3541
3718
  * @param {Environment} envs
3542
- * @param {Record<string, string | Layout.Calc>} classes
3719
+ * @param {Record<string, Layout.Node.Name | Layout.Node.Calc | Layout.Node.Value>} styles
3720
+ * @param {Layout.Node.Name | Layout.Node.Calc | Layout.Node.Value} [styleAttr]
3543
3721
  */
3544
- function bindStyles(node, classes, envs) {
3722
+ function bindStyles(node, envs, styles, styleAttr) {
3545
3723
  if (!(node instanceof HTMLElement) && !(node instanceof SVGElement)) {
3546
3724
  return () => {};
3547
3725
  }
3726
+ if (styleAttr) {
3727
+ node.setAttribute('style', node.getAttribute('style') + ';' + envs.exec(styleAttr));
3728
+ }
3548
3729
 
3549
3730
  /** @type {Set<() => void>?} */
3550
3731
  let bk = new Set();
3551
- for (const [name, attr] of Object.entries(classes)) {
3732
+ for (const [name, attr] of Object.entries(styles)) {
3552
3733
  bk.add(watch(() => toStyle(name, envs.exec(attr)), value => {
3553
3734
  if (value) {
3554
3735
  node.style.setProperty(name, ...value);
@@ -3610,9 +3791,10 @@
3610
3791
  }
3611
3792
  }
3612
3793
 
3613
- /** @import { Component } from '../types.mjs' */
3794
+ /** @import { Component, Relatedness } from '../types.mjs' */
3614
3795
  /** @import Store from '../Store/index.mjs' */
3615
3796
  /** @import Environment from './Environment/index.mjs' */
3797
+ /** @import * as Layout from '../Layout/index.mjs' */
3616
3798
 
3617
3799
 
3618
3800
  /** @type {Record<string, Component.Event.Filter>} */
@@ -3732,14 +3914,68 @@
3732
3914
  }
3733
3915
  },
3734
3916
  };
3917
+ /**
3918
+ *
3919
+ * @param {string[]} fs
3920
+ * @param {Record<string, string | Component.Event.Filter>} filters
3921
+ * @param {AddEventListenerOptions?} [options]
3922
+ * @returns
3923
+ */
3924
+ function findFilters(fs, filters, options) {
3925
+ /** @type {[Component.Event.Filter, string[], boolean][]} */
3926
+ const filterFns = [];
3927
+ if (filters) for (let f = fs.shift(); f; f = fs.shift()) {
3928
+ const paramIndex = f.indexOf(':');
3929
+ const noParamName = paramIndex >= 0 ? f.slice(0, paramIndex) : f;
3930
+ const param = paramIndex >= 0 ? f.slice(paramIndex + 1).split(':') : [];
3931
+ const filterName = noParamName.replace(/^-+/, '');
3932
+ const sub = (noParamName.length - filterName.length) % 2 === 1;
3933
+ let filter = filters[filterName] || filterName;
3934
+ if (options) {
3935
+ switch (filter) {
3936
+ case 'once': case 'passive': case 'capture': options[filter] = !sub; continue;
3937
+ }
3938
+ }
3939
+ if (typeof filter === 'string') {
3940
+ filter = eventFilters[filter];
3941
+ }
3942
+ if (typeof filter !== 'function') { continue; }
3943
+ filterFns.push([filter, param, sub]);
3944
+ }
3945
+ return filterFns;
3946
+ }
3947
+
3948
+ /**
3949
+ *
3950
+ * @param {Environment} env
3951
+ * @param {Layout.EventListener} fn
3952
+ * @param {[Component.Event.Filter, string[], boolean][]} filterFns
3953
+ * @returns {($event: any) => void}
3954
+ */
3955
+ function bindFilters(env, fn, filterFns) {
3956
+ return $event => {
3957
+ const global = env.all;
3958
+ for (const [filter, param, sub] of filterFns) {
3959
+ if (filter($event, param, global) === sub) { return; }
3960
+ }
3961
+ fn($event, global);
3962
+ }
3963
+ }
3964
+
3965
+ /** @import { Component, Relatedness } from '../types.mjs' */
3966
+ /** @import Store from '../Store/index.mjs' */
3967
+ /** @import Environment from './Environment/index.mjs' */
3968
+
3969
+
3735
3970
  /**
3736
3971
  *
3737
3972
  * @param {Component | string} component
3738
3973
  * @param {Environment} env
3739
- * @param {((store: Store, el: Element) => () => void)?} [relate]
3974
+ * @param {Store?} store
3975
+ * @param {((store: Store, el: Element | Relatedness) => () => void)?} [relate]
3740
3976
  * @returns
3741
3977
  */
3742
- function createContext(component, env, relate) {
3978
+ function createContext(component, env, store, relate) {
3743
3979
  const tag = typeof component === 'string' ? component : component.tag;
3744
3980
  const { attrs, events } = typeof component !== 'string' && component || { attrs: null, events: null };
3745
3981
 
@@ -3780,9 +4016,9 @@
3780
4016
  };
3781
4017
  },
3782
4018
  relate(el) {
3783
- if (!relate || destroyed) { return () => { }; }
4019
+ if (!store || !relate || destroyed) { return () => { }; }
3784
4020
  try {
3785
- const w = relate(env.store, el);
4021
+ const w = relate(store, el);
3786
4022
  if (typeof w !== 'function') { return () => { }; }
3787
4023
  cancelFns.add(w);
3788
4024
  return () => {
@@ -3823,40 +4059,8 @@
3823
4059
  if (!filters) { return; }
3824
4060
  /** @type {AddEventListenerOptions} */
3825
4061
  const options = {};
3826
- /** @type {[Component.Event.Filter, string[], boolean][]} */
3827
- const filterFns = [];
3828
- if (filters) for (let f = fs.shift(); f; f = fs.shift()) {
3829
- const paramIndex = f.indexOf(':');
3830
- const noParamName = paramIndex >= 0 ? f.slice(0, paramIndex) : f;
3831
- const param = paramIndex >= 0 ? f.slice(paramIndex + 1).split(':') : [];
3832
- const filterName = noParamName.replace(/^-+/, '');
3833
- const sub = (noParamName.length - filterName.length) % 2 === 1;
3834
- let filter = filters[filterName] || filterName;
3835
- switch (filter) {
3836
- case 'once':
3837
- options.once = !sub;
3838
- break;
3839
- case 'passive':
3840
- options.passive = !sub;
3841
- break;
3842
- case 'capture':
3843
- options.capture = !sub;
3844
- break;
3845
- default:
3846
- if (typeof filter === 'string') {
3847
- filter = eventFilters[filter];
3848
- }
3849
- }
3850
- if (typeof filter !== 'function') { continue; }
3851
- filterFns.push([filter, param, sub]);
3852
- }
3853
- allEvents.push([e, $event => {
3854
- const global = env.all;
3855
- for (const [filter, param, sub] of filterFns) {
3856
- if (filter($event, param, global) === sub) { return; }
3857
- }
3858
- fn($event, global);
3859
- }, options]);
4062
+ const filterFns = findFilters(fs, filters, options);
4063
+ allEvents.push([e, bindFilters(env, fn, filterFns), options]);
3860
4064
  },
3861
4065
  destroy() {
3862
4066
  if (destroyed) { return; }
@@ -4046,6 +4250,9 @@
4046
4250
  function createTagComponent (context, name, is) {
4047
4251
  const node = document.createElement(name, {is: is || undefined});
4048
4252
  const { watchAttr, props } = context;
4253
+ if(['input', 'textarea', 'select'].includes(name.toLowerCase())) {
4254
+ context.relate(node);
4255
+ }
4049
4256
 
4050
4257
  context.listen('init', ({events})=> {
4051
4258
  const e = tagBindMap[name.toLowerCase()];
@@ -4224,7 +4431,7 @@
4224
4431
  * @param {Element} parent
4225
4432
  * @param {Node?} next
4226
4433
  * @param {Environment} envs
4227
- * @param {Layout.Directives} layout
4434
+ * @param {Layout.Node} layout
4228
4435
  */
4229
4436
  function renderFillDirectives(parent, next, envs, { text, html }) {
4230
4437
  if (text != null) {
@@ -4278,18 +4485,18 @@
4278
4485
 
4279
4486
  /** @type {Set<() => void>?} */
4280
4487
  let bkList = new Set();
4281
- /** @type {[string | Layout.Calc | null, Layout.Node][]} */
4488
+ /** @type {[Layout.Node.Name | Layout.Node.Calc | Layout.Node.Value | null, Layout.Node][]} */
4282
4489
  let ifList = [];
4283
4490
  /** @type {Record<string, [Layout.Node, Environment]>} */
4284
4491
  let currentTemplates = Object.create(templates);
4285
4492
  for (const layout of layouts) {
4286
4493
  if (typeof layout === 'string') { continue; }
4287
- const name = layout.directives.template;
4494
+ const name = layout.template;
4288
4495
  if (!name) { continue; }
4289
4496
  currentTemplates[name] = [layout, envs];
4290
4497
  }
4291
4498
 
4292
- /** @param {[string | Layout.Calc | null, Layout.Node][]} list */
4499
+ /** @param {[Layout.Node.Name | Layout.Node.Calc | Layout.Node.Value | null, Layout.Node][]} list */
4293
4500
  function renderIf(list) {
4294
4501
  if (!list.length || !bkList) { return; }
4295
4502
  const end = parent.insertBefore(document.createComment(''), next);
@@ -4313,7 +4520,7 @@
4313
4520
  end.remove();
4314
4521
  });
4315
4522
  bkList.add(watch(
4316
- () => list.findIndex(([ifv]) => ifv === null || envs.exec(ifv)),
4523
+ () => list.findIndex(([ifv]) => !ifv || envs.exec(ifv)),
4317
4524
  index => {
4318
4525
  if (index === lastIndex) { return; }
4319
4526
  lastIndex = index;
@@ -4332,13 +4539,13 @@
4332
4539
  bkList.add(() => node.remove());
4333
4540
  continue;
4334
4541
  }
4335
- if (layout.directives.template) {
4542
+ if (layout.template) {
4336
4543
  renderIf(ifList);
4337
4544
  ifList = [];
4338
4545
  continue;
4339
4546
  }
4340
- if (ifList.length && layout.directives.else) {
4341
- const ifv = layout.directives.if || null;
4547
+ if (ifList.length && layout.else) {
4548
+ const ifv = layout.if || null;
4342
4549
  ifList.push([ifv, layout]);
4343
4550
  if (!ifv) {
4344
4551
  renderIf(ifList);
@@ -4348,7 +4555,7 @@
4348
4555
  }
4349
4556
  renderIf(ifList);
4350
4557
  ifList = [];
4351
- const ifv = layout.directives.if;
4558
+ const ifv = layout.if;
4352
4559
  if (ifv) {
4353
4560
  ifList.push([ifv, layout]);
4354
4561
  continue;
@@ -4357,6 +4564,7 @@
4357
4564
  renderItem(layout, currentTemplates)
4358
4565
  );
4359
4566
  }
4567
+ renderIf(ifList);
4360
4568
 
4361
4569
  return () => {
4362
4570
  if (!bkList) { return; }
@@ -4503,6 +4711,135 @@
4503
4711
  }
4504
4712
  }
4505
4713
 
4714
+ /** @import Environment from './Environment/index.mjs' */
4715
+ /** @import { Enhancement } from '../types.mjs' */
4716
+ /** @import * as Layout from '../Layout/index.mjs' */
4717
+
4718
+ /**
4719
+ *
4720
+ * @param {object} obj
4721
+ * @param {string} name
4722
+ * @returns
4723
+ */
4724
+ const hasOwnProperty = (obj, name) => Object.prototype.hasOwnProperty.call(obj, name);
4725
+ /**
4726
+ * @param {any} tag
4727
+ * @param {Record<string, Layout.Enhancement>} enhancementDefine
4728
+ * @param {Environment} env
4729
+ * @param {Record<string, Enhancement>} enhancements
4730
+ * @param {Element} root
4731
+ * @param {Element?} [slot]
4732
+ */
4733
+ function bindEnhancements(tag, enhancementDefine, env, enhancements, root, slot) {
4734
+
4735
+ let bk = new Set();
4736
+ for (const [name, { attrs: attrDefine, value, events: eventsDefine, bind }] of Object.entries(enhancementDefine)) {
4737
+ if (!hasOwnProperty(enhancements, name)) { continue; }
4738
+ const enhancement = enhancements[name];
4739
+ if (typeof enhancement !== 'function') { continue; }
4740
+
4741
+
4742
+ let destroyed = false;
4743
+ const destroyedState = new exports.Signal.State(false);
4744
+ const events = /** @type {any} */(enhancement)?.events;
4745
+ /** @type {[string, ($event: any) => void, AddEventListenerOptions][]} */
4746
+ const allEvents = [];
4747
+ const attrs = Object.create(null);
4748
+ /** @type {Set<() => void>} */
4749
+ const cancelFns = new Set();
4750
+ const stateEmitter = new EventEmitter();
4751
+
4752
+ /**
4753
+ *
4754
+ * @param {string} name
4755
+ * @param {Layout.EventListener} fn
4756
+ * @returns
4757
+ */
4758
+ function addEvent(name, fn) {
4759
+ if (typeof fn !== 'function') { return; }
4760
+ const [e, ...fs] = name.split('.').filter(Boolean);
4761
+ const filters = events ? events[e].filters : {};
4762
+ if (!filters) { return; }
4763
+ /** @type {AddEventListenerOptions} */
4764
+ const options = {};
4765
+ const filterFns = findFilters(fs, filters, options);
4766
+ allEvents.push([e, bindFilters(env, fn, filterFns), options]);
4767
+ }
4768
+ function destroy() {
4769
+ if (destroyed) { return; }
4770
+ destroyed = true;
4771
+ destroyedState.set(true);
4772
+ for (const w of cancelFns) {
4773
+ w();
4774
+ }
4775
+ stateEmitter.emit('destroy');
4776
+ }
4777
+ bk.add(destroy);
4778
+
4779
+
4780
+
4781
+ for (const [name, attr] of Object.entries(attrDefine)) {
4782
+ const s = env.get(attr);
4783
+ if (!s) { continue; }
4784
+ Object.defineProperty(attrs, name, { ...s, configurable: true, enumerable: true });
4785
+ }
4786
+
4787
+ for (const [name, event] of Object.entries(eventsDefine)) {
4788
+ const fn = env.getEvent(event);
4789
+ if (fn) { addEvent(name, fn); }
4790
+ }
4791
+
4792
+ if (bind) {
4793
+ for (const [key, effect] of Object.entries(env.getBindAll(bind) || {})) {
4794
+ Object.defineProperty(attrs, key, { ...effect, configurable: true, enumerable: true });
4795
+ }
4796
+ for (const [key, setter] of Object.entries(env.bindEvents(bind) || {})) {
4797
+ addEvent(key, $event => setter($event));
4798
+ }
4799
+
4800
+ }
4801
+ /**@type {Enhancement.Context} */
4802
+ const context = {
4803
+ get value() { return null; },
4804
+ events: allEvents,
4805
+ attrs: attrs,
4806
+ watchAttr(name, fn) {
4807
+ if (destroyed) { return () => { }; }
4808
+ let old = attrs[name];
4809
+ const w = watch(() => attrs[name], v => {
4810
+ const o = old;
4811
+ old = v;
4812
+ fn(v, o, name);
4813
+ }, true);
4814
+ cancelFns.add(w);
4815
+
4816
+ return () => {
4817
+ cancelFns.delete(w);
4818
+ w();
4819
+ };
4820
+ },
4821
+ get destroyed() { return destroyedState.get(); },
4822
+ listen(name, listener) { return stateEmitter.listen(name, listener); },
4823
+ root, slot,
4824
+ tag,
4825
+ };
4826
+ if (value) {
4827
+ const s = env.get(value);
4828
+ if (s) {
4829
+ Object.defineProperty(context, 'value', { ...s, configurable: true, enumerable: true });
4830
+ }
4831
+ }
4832
+ enhancement(context);
4833
+ }
4834
+ return () => {
4835
+ const list = bk;
4836
+ bk = new Set();
4837
+ for (const s of list) {
4838
+ s();
4839
+ }
4840
+ };
4841
+ }
4842
+
4506
4843
  /** @import Store from '../Store/index.mjs' */
4507
4844
 
4508
4845
  /**
@@ -4512,31 +4849,36 @@
4512
4849
  * @param {Environment} env
4513
4850
  * @param {Record<string, [Layout.Node, Environment]>} templates
4514
4851
  * @param {string[]} componentPath
4515
- * @param {((store: Store, el: Element) => () => void)?} [relate]
4852
+ * @param {Record<string, Enhancement>} enhancements
4853
+ * @param {((store: Store, el: Element | Relatedness) => () => void)?} [relate]
4516
4854
  * @param {Component.Getter?} [getComponent]
4517
4855
  */
4518
- function renderItem(layout, parent, next, env, templates, componentPath, relate, getComponent) {
4856
+ function renderItem(layout, parent, next, env, templates, componentPath, enhancements, relate, getComponent) {
4519
4857
  env = env.set(layout.aliases, layout.vars);
4520
- const bind = layout.directives.bind;
4521
- const fragment = layout.directives.fragment;
4858
+ const bind = layout.bind;
4859
+ const fragment = layout.fragment;
4522
4860
  if (fragment && typeof fragment === 'string') {
4523
4861
  const template = templates[fragment];
4524
4862
  if (!template) { return () => {}; }
4525
4863
  const [templateLayout, templateEnv] = template;
4526
4864
  const newEnv = templateEnv.params(templateLayout, layout, env, bind);
4527
- return render(templateLayout, parent, next, newEnv, templates, componentPath, relate, getComponent);
4865
+ return render(templateLayout, parent, next, newEnv, templates, componentPath, enhancements, relate, getComponent);
4528
4866
  }
4529
- if (!layout.name || layout.directives.fragment) {
4530
- return renderFillDirectives(parent, next, env, layout.directives) ||
4867
+ if (!layout.name || layout.fragment) {
4868
+ return renderFillDirectives(parent, next, env, layout) ||
4531
4869
  renderList(layout.children || [], parent, next, env, templates, (layout, templates) => {
4532
- return render(layout, parent, next, env, templates, componentPath, relate, getComponent);
4870
+ return render(layout, parent, next, env, templates, componentPath, enhancements, relate, getComponent);
4533
4871
  });
4534
4872
  }
4535
4873
  const path = [...componentPath, layout.name];
4536
4874
  const component = getComponent?.(path);
4537
4875
  if (getComponent && !component) { return () => { }; }
4538
- const { context, handler } = createContext(component ? component : layout.name, env, relate);
4539
-
4876
+ const { context, handler } = createContext(
4877
+ component ? component : layout.name,
4878
+ env,
4879
+ env.getStore(bind),
4880
+ relate
4881
+ );
4540
4882
 
4541
4883
  const componentAttrs = component?.attrs;
4542
4884
  const attrs = componentAttrs
@@ -4560,23 +4902,27 @@
4560
4902
  const slot = Array.isArray(r) ? r[1] : root;
4561
4903
  parent.insertBefore(root, next);
4562
4904
  const children = slot ?
4563
- renderFillDirectives(slot, null, env, layout.directives)
4905
+ renderFillDirectives(slot, null, env, layout)
4564
4906
  || renderList(layout.children || [], slot, null, env, templates, (layout, templates) => {
4565
- return render(layout, slot, null, env, templates, componentPath, relate, getComponent);
4907
+ return render(layout, slot, null, env, templates, componentPath, enhancements, relate, getComponent);
4566
4908
  }) : () => {};
4567
4909
 
4568
4910
 
4569
- bindClasses(root, layout.classes, env);
4570
- bindStyles(root, layout.styles, env);
4911
+ const classes = bindClasses(root, env, layout.classes, layout.attrs.class);
4912
+ const styles = bindStyles(root, env, layout.styles, layout.attrs.style);
4571
4913
 
4572
4914
  handler.mount();
4915
+ const enhancement = bindEnhancements(handler.tag, layout.enhancements, env, enhancements, root, slot);
4573
4916
 
4574
4917
  return () => {
4575
- root.remove();
4918
+ enhancement();
4576
4919
  handler.destroy();
4920
+ root.remove();
4577
4921
  attrs();
4578
4922
  children();
4579
4923
  base();
4924
+ classes();
4925
+ styles();
4580
4926
  };
4581
4927
  }
4582
4928
  /**
@@ -4587,17 +4933,17 @@
4587
4933
  * @param {Environment} env
4588
4934
  * @param {Record<string, [Layout.Node, Environment]>} templates
4589
4935
  * @param {string[]} componentPath
4590
- * @param {((store: Store, el: Element) => () => void)?} [relate]
4936
+ * @param {Record<string, Enhancement>} enhancements
4937
+ * @param {((store: Store, el: Element | Relatedness) => () => void)?} [relate]
4591
4938
  * @param {Component.Getter?} [getComponent]
4592
4939
  * @returns {() => void}
4593
4940
  */
4594
- function render(layout, parent, next, env, templates, componentPath, relate, getComponent) {
4595
- const { directives } = layout;
4596
- const newEnv = env.child(directives.value);
4941
+ function render(layout, parent, next, env, templates, componentPath, enhancements, relate, getComponent) {
4942
+ const newEnv = env.child(layout.value);
4597
4943
  if (!newEnv) { return () => {}; }
4598
- const list = newEnv.enum(directives.enum);
4944
+ const list = newEnv.enum(layout.enum);
4599
4945
  /** @type {(next: Node | null, env: any) => () => void} */
4600
- const r = (next, env) => renderItem(layout, parent, next, env, templates, componentPath, relate, getComponent);
4946
+ const r = (next, env) => renderItem(layout, parent, next, env, templates, componentPath, enhancements, relate, getComponent);
4601
4947
  if (list === true) {
4602
4948
  return r(next, newEnv);
4603
4949
  }
@@ -4620,15 +4966,17 @@
4620
4966
  * @param {object} [options]
4621
4967
  * @param {Record<string, Store | {get?(): any; set?(v: any): void; exec?(...p: any[]): any; calc?(...p: any[]): any }>} [options.global]
4622
4968
  * @param {(path: string[]) => Component?} [options.component]
4623
- * @param {(store: Store, el: Element) => () => void} [options.relate]
4969
+ * @param {(store: Store, el: Element | Relatedness) => () => void} [options.relate]
4970
+ * @param {Record<string, Enhancement>} [options.enhancements]
4624
4971
  * @returns {() => void}
4625
4972
  */
4626
- function index (store, layouts, parent, {component, global, relate} = {}) {
4973
+ function index (store, layouts, parent, {component, global, relate, enhancements} = {}) {
4627
4974
  const env = new Environment(store, global);
4628
4975
  const templates = Object.create(null);
4976
+ const allEnhancements = enhancements || {};
4629
4977
  const relateFn = typeof relate === 'function' ? relate : null;
4630
4978
  return renderList(layouts, parent, null, env, templates, (layout, templates) => {
4631
- return render(layout, parent, null, env, templates, [], relateFn, component);
4979
+ return render(layout, parent, null, env, templates, [], allEnhancements, relateFn, component);
4632
4980
  });
4633
4981
  }
4634
4982