arrmatura 4.3.2 → 5.0.4

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.
Files changed (92) hide show
  1. package/README.md +30 -25
  2. package/commons/auth/UserService.ts +107 -0
  3. package/commons/auth/index.ts +4 -0
  4. package/commons/auth/user.xml +66 -0
  5. package/commons/components/buttons.xml +23 -0
  6. package/commons/components/index.ts +13 -0
  7. package/commons/components/inputs.xml +33 -0
  8. package/commons/components/layouts.xml +77 -0
  9. package/commons/components/media.xml +21 -0
  10. package/commons/components/modal.xml +37 -0
  11. package/commons/components/navigation.xml +59 -0
  12. package/commons/components/popups.xml +12 -0
  13. package/commons/components/searchbar.xml +40 -0
  14. package/commons/components/table.xml +120 -0
  15. package/commons/components/toasts.xml +13 -0
  16. package/commons/components/viewport.xml +48 -0
  17. package/commons/forms/FieldItem.xml +20 -0
  18. package/commons/forms/FormController.ts +73 -0
  19. package/commons/forms/Item.ts +68 -0
  20. package/commons/forms/ItemCollection.ts +152 -0
  21. package/commons/forms/ItemController.ts +62 -0
  22. package/commons/forms/MetadataService.ts +45 -0
  23. package/commons/forms/MultiEnumField.xml +44 -0
  24. package/commons/forms/MultivalueController.ts +49 -0
  25. package/commons/forms/SmartareaField.xml +51 -0
  26. package/commons/forms/SuggestionController.ts +80 -0
  27. package/commons/forms/SuggestionField.xml +28 -0
  28. package/commons/forms/fields.xml +138 -0
  29. package/commons/forms/form.xml +37 -0
  30. package/commons/forms/index.ts +29 -0
  31. package/commons/index.ts +6 -0
  32. package/commons/pipes/index.ts +21 -0
  33. package/commons/pipes/showCounts.ts +16 -0
  34. package/commons/services/ErrorHandlingService.ts +11 -0
  35. package/commons/services/FirebaseService.ts +185 -0
  36. package/commons/services/GSheetsService.ts +92 -0
  37. package/commons/services/HeartbeatService.ts +35 -0
  38. package/commons/services/Invoke.ts +13 -0
  39. package/commons/services/JsonpService.ts +18 -0
  40. package/commons/services/Loader.ts +17 -0
  41. package/commons/services/LocalStorage.ts +46 -0
  42. package/commons/services/NavigationService.ts +55 -0
  43. package/commons/services/Service.ts +68 -0
  44. package/commons/services/ServiceWorker.ts +111 -0
  45. package/commons/services/ToastService.ts +49 -0
  46. package/commons/services/index.ts +14 -0
  47. package/core/Arrmatura.ts +84 -0
  48. package/core/CNode.ts +112 -0
  49. package/core/Component.ts +426 -0
  50. package/core/cnodes/composition.ts +100 -0
  51. package/core/cnodes/conditionals.ts +51 -0
  52. package/core/cnodes/elementary.ts +44 -0
  53. package/core/cnodes/iterations.ts +92 -0
  54. package/core/cnodes/routing.ts +25 -0
  55. package/core/compileNode.ts +39 -0
  56. package/core/expression.ts +73 -0
  57. package/core/index.ts +1 -0
  58. package/core/register.ts +43 -0
  59. package/core/utils.ts +25 -0
  60. package/core-web/WebPlatform.ts +25 -0
  61. package/core-web/attributes.ts +190 -0
  62. package/core-web/elements.ts +63 -0
  63. package/core-web/index.ts +16 -0
  64. package/core-web/reflow.ts +28 -0
  65. package/core-web/type.ts +18 -0
  66. package/docs/index.js +187 -4069
  67. package/lib/arrayGroupBy.ts +19 -0
  68. package/lib/arraySortBy.ts +49 -0
  69. package/lib/arrayToObject.ts +18 -0
  70. package/lib/codus.ts +25 -0
  71. package/lib/consts.ts +3 -0
  72. package/lib/createListeners.ts +12 -0
  73. package/lib/curry.ts +8 -0
  74. package/lib/dateFormat.ts +69 -0
  75. package/lib/dateOf.ts +31 -0
  76. package/lib/dateParseISO8601.ts +27 -0
  77. package/lib/fx.ts +62 -0
  78. package/lib/guid.ts +7 -0
  79. package/lib/index.ts +19 -0
  80. package/lib/l10n.ts +40 -0
  81. package/lib/nextId.ts +9 -0
  82. package/lib/noop.ts +8 -0
  83. package/lib/obj.ts +64 -0
  84. package/lib/predicat.ts +26 -0
  85. package/lib/resources.ru.ts +20 -0
  86. package/lib/resources.ts +20 -0
  87. package/lib/scalar.ts +25 -0
  88. package/lib/str.ts +76 -0
  89. package/lib/urls.ts +91 -0
  90. package/lib/xml.ts +117 -0
  91. package/package.json +18 -8
  92. package/docs/index.js.map +0 -1
@@ -0,0 +1,44 @@
1
+ import { CNode } from '../CNode';
2
+ import { compileNodes } from '../compileNode';
3
+ import { Component } from '../Component';
4
+
5
+ export class ElementComponent extends Component<CElementNode> {
6
+ get displayName(): string {
7
+ return this.node.tag;
8
+ }
9
+
10
+ createImplementation(initials: Hash): IComponentImplementation {
11
+ return this.arrmatura.platform.createElement(this.node.tag, initials, this);
12
+ }
13
+ }
14
+
15
+ export class TextElementComponent extends Component<CTextElementNode> {
16
+ createImplementation(initials: Hash) {
17
+ return this.arrmatura.platform.createTextElement(initials, this);
18
+ }
19
+ }
20
+
21
+ export class CElementNode extends CNode<ElementComponent> {
22
+ constructor(x: XmlNode) {
23
+ super();
24
+ this.tag = x.tag;
25
+ this.content = compileNodes(x.nodes);
26
+ this.compileAttributes(x.attrs);
27
+ }
28
+
29
+ get ComponentConstructor() {
30
+ return ElementComponent;
31
+ }
32
+ }
33
+
34
+ export class CTextElementNode extends CNode<TextElementComponent> {
35
+ constructor(x: XmlNode) {
36
+ super();
37
+ this.tag = x.tag;
38
+ this.compileAttributes(x.attrs);
39
+ }
40
+
41
+ get ComponentConstructor() {
42
+ return TextElementComponent;
43
+ }
44
+ }
@@ -0,0 +1,92 @@
1
+ import { CNode } from '../CNode';
2
+ import { compileNodes } from '../compileNode';
3
+ import { Component } from '../Component';
4
+
5
+ class ForComponent extends Component<CForNode> {
6
+ pkHash: Hash<Datum> = {};
7
+
8
+ get displayName(): string {
9
+ return '🔹';
10
+ }
11
+
12
+ get content() {
13
+ const each: any = this.$impl.each;
14
+ const itemNode = this.node.itemNode;
15
+ const nodes: ComponentContent = new Map();
16
+ this.pkHash = {};
17
+ if (each?.length) {
18
+ if (!each.forEach) {
19
+ throw new Error('[ui:for] Items has no forEach() ' + each);
20
+ }
21
+ each.forEach((datum: Datum) => {
22
+ const id = datum.id;
23
+ if (id == null) {
24
+ console.error('ERROR: empty item id: ', datum);
25
+ return;
26
+ }
27
+ const pk = String(id);
28
+ if (this.pkHash[pk]) {
29
+ console.error('ERROR: duplicate item id: ' + pk, datum);
30
+ return;
31
+ }
32
+ this.pkHash[pk] = datum;
33
+ const inode = itemNode.cloneWithDatum(datum).addPropertyResolver(() => this.pkHash[pk], itemNode.itemName);
34
+ nodes.set(inode.uid, inode);
35
+ });
36
+ }
37
+ return nodes;
38
+ }
39
+ }
40
+
41
+ export class CForNode extends CNode<ForComponent> {
42
+ itemNode: CItemNode;
43
+ constructor(x: XmlNode, [itemName, prep, expr = prep]: string[]) {
44
+ super();
45
+ this.itemNode = new CItemNode(itemName, compileNodes([x]));
46
+ this.compileAttribute('each', '{:<'.includes(expr[0]) ? expr : `{${expr}}`);
47
+ }
48
+
49
+ get ComponentConstructor() {
50
+ return ForComponent;
51
+ }
52
+ }
53
+
54
+ class ItemComponent extends Component<CItemNode> {
55
+ get displayName(): string {
56
+ return '🔹';
57
+ }
58
+ get recontentScope(): Component {
59
+ return this;
60
+ }
61
+
62
+ emit(key: string, data: Delta): void {
63
+ return this.$scope.emit(key, data);
64
+ }
65
+
66
+ get(propId: string) {
67
+ const iName: string = this.node.itemName;
68
+ const value = propId.startsWith(iName + '.') || propId === iName ? super.get(propId) : this.$scope.get(propId);
69
+ // this.log(propId, value);
70
+ return value;
71
+ }
72
+ }
73
+
74
+ export class CItemNode extends CNode<ItemComponent> {
75
+ itemName: string;
76
+ constructor(itemName: string, content?: ComponentContent) {
77
+ super();
78
+ this.itemName = itemName;
79
+ this.content = content;
80
+ }
81
+
82
+ cloneWithDatum(datum: Datum) {
83
+ const uid = this.uid + '#' + datum.id;
84
+ return Object.assign(new CItemNode(this.itemName, this.content), {
85
+ uid,
86
+ });
87
+ }
88
+
89
+ get ComponentConstructor() {
90
+ return ItemComponent;
91
+ }
92
+ }
@@ -0,0 +1,25 @@
1
+ import { CNode } from '../CNode';
2
+ import { compileNode } from '../compileNode';
3
+ import { Component } from '../Component';
4
+
5
+ export class DynamicTagComponent extends Component<CDynamicTagNode> {
6
+ get content(): ComponentContent | undefined {
7
+ const tag = this.$impl.tag;
8
+ const uid = this.node.uid + ':' + tag;
9
+ return new Map([[uid, Object.assign(compileNode(Object.assign(this.node.xml, { tag })), { uid })]]);
10
+ }
11
+ }
12
+
13
+ export class CDynamicTagNode extends CNode<DynamicTagComponent> {
14
+ xml: XmlNode & { tag: string };
15
+
16
+ constructor(xml: XmlNode, expr: string) {
17
+ super();
18
+ this.compileAttribute('tag', expr);
19
+ this.xml = xml;
20
+ }
21
+
22
+ get ComponentConstructor() {
23
+ return DynamicTagComponent;
24
+ }
25
+ }
@@ -0,0 +1,39 @@
1
+ import { CCompositeNode, CFragmentNode, CSlotNode } from './cnodes/composition';
2
+ import { CDynamicTagNode } from './cnodes/routing';
3
+ import { CElementNode, CTextElementNode } from './cnodes/elementary';
4
+ import { CForNode } from './cnodes/iterations';
5
+ import { CIfNode } from './cnodes/conditionals';
6
+
7
+ export const compileNode = (x: XmlNode): INode => {
8
+ const { tag, attrs } = x;
9
+ const each = attrs?.['ui:for'];
10
+ if (each) {
11
+ delete attrs['ui:for'];
12
+ return new CForNode(x, String(each).trim().split(' '));
13
+ }
14
+ const when = attrs?.['ui:if'];
15
+ if (when) {
16
+ delete attrs['ui:if'];
17
+ return new CIfNode(x, String(when));
18
+ }
19
+ if (tag === 'ui:tag') {
20
+ const expr = attrs?.['tag'] || tag;
21
+ delete attrs?.['tag'];
22
+ return new CDynamicTagNode(x, String(expr));
23
+ }
24
+ if (tag === 'ui:slot') {
25
+ return new CSlotNode(x);
26
+ }
27
+ if (tag === 'ui:fragment') {
28
+ return new CFragmentNode(x);
29
+ }
30
+ if (tag === '#text') {
31
+ return new CTextElementNode(x);
32
+ }
33
+ if (tag[0].match(/[A-Z]/)) return new CCompositeNode(x);
34
+
35
+ return new CElementNode(x);
36
+ };
37
+
38
+ export const compileNodes = (x?: XmlNode[] | null): ComponentContent =>
39
+ new Map<Uid, INode>(x?.map(compileNode).map((c) => [c.uid, c]) || []);
@@ -0,0 +1,73 @@
1
+ import { scalarParse } from 'scalar';
2
+
3
+ const RE_SINGLE_PLACEHOLDER = /^\{([^}]+)\}$/;
4
+
5
+ const RE_PLACEHOLDER = /\{([^}]+)\}/g;
6
+
7
+ const withNoPipes = (_: unknown, v: unknown) => v;
8
+
9
+ const splitExpression = (v: Expression) => {
10
+ return v.split('|').map((s) => s.trim());
11
+ };
12
+
13
+ export function withPipes(pipes: string[]) {
14
+ return !pipes.length
15
+ ? withNoPipes
16
+ : (c: IComponent, initialValue: unknown) => pipes.reduce((r, pk) => c.applyPipe(pk, r), initialValue);
17
+ }
18
+
19
+ export function compilePipeExpression(v: Expression) {
20
+ const [key, ...pipes] = splitExpression(v);
21
+ return { key, pipec: withPipes(pipes) };
22
+ }
23
+
24
+ function stringInterpolation(v: string, fnx: Array<(c: IComponent) => unknown> = []) {
25
+ const pattern = v.trim().replace(RE_PLACEHOLDER, (_: string, expr: string) => {
26
+ fnx.push(placeholder(expr));
27
+ return '{{' + (fnx.length - 1) + '}}';
28
+ });
29
+ return !fnx.length
30
+ ? (_: IComponent) => pattern
31
+ : (c: IComponent) =>
32
+ pattern
33
+ .replace(/\{\{(\d+)\}\}/g, (_: string, idx: string) => {
34
+ const r = fnx[Number(idx)](c);
35
+ return !r && r !== 0 ? '' : String(r);
36
+ })
37
+ .replace(/\s+/g, ' ');
38
+ }
39
+
40
+ function resourceExpression(expr: string) {
41
+ const { key, pipec } = compilePipeExpression(expr);
42
+ const intrpltn = stringInterpolation(key);
43
+ return !pipec.length ? (c: IComponent) => c.res(intrpltn(c)) : (c: IComponent) => pipec(c, c.res(intrpltn(c)));
44
+ }
45
+
46
+ function placeholder(expr: string) {
47
+ const { key, pipec } = compilePipeExpression(expr);
48
+ const fn =
49
+ key[0] === ':'
50
+ ? (
51
+ (k) => (c: IComponent) =>
52
+ c.res(k)
53
+ )(key.slice(1))
54
+ : (c: IComponent) => c.getFromScope(key);
55
+ return !pipec.length ? fn : (c: IComponent) => pipec(c, fn(c));
56
+ }
57
+
58
+ export function compileExpression(v: unknown) {
59
+ if (typeof v !== 'string') {
60
+ return () => v;
61
+ }
62
+ if (v[0] === ':') {
63
+ return resourceExpression(v.slice(1).trim());
64
+ }
65
+ if (!v.includes('{')) {
66
+ const vv = scalarParse(v);
67
+ return () => vv;
68
+ }
69
+ if (v.match(RE_SINGLE_PLACEHOLDER)) {
70
+ return placeholder(v.slice(1, -1).trim());
71
+ }
72
+ return stringInterpolation(v);
73
+ }
package/core/index.ts ADDED
@@ -0,0 +1 @@
1
+ export * from './Arrmatura';
@@ -0,0 +1,43 @@
1
+ import { compileNodes } from './compileNode';
2
+ import { xmlParse } from 'xml';
3
+ import { fnName } from './utils';
4
+
5
+ export class ComponentDefinition {
6
+ tag: string;
7
+ template?: string;
8
+ Ctor: any;
9
+ $templateContent?: ComponentContent;
10
+
11
+ constructor({ tag, template, Ctor }: ComponentDefinitionInfo) {
12
+ this.tag = tag;
13
+ this.template = template;
14
+ this.Ctor = Ctor || function () {};
15
+ }
16
+
17
+ get content() {
18
+ return this.$templateContent || (this.$templateContent = compileNodes(xmlParse(this.template)));
19
+ }
20
+ }
21
+
22
+ export const REGISTRY = new Map<string, ComponentDefinition>();
23
+
24
+ const notFound = (tag: string): ComponentDefinition =>
25
+ new ComponentDefinition({ tag, template: '<b>??' + tag + '??</b>' });
26
+
27
+ const setInfo = (ctr: ComponentDefinitionInfo) => REGISTRY.set(ctr.tag, new ComponentDefinition(ctr));
28
+
29
+ const registerType = (ctr: string | ComponentDefinitionInfo | Constructor) => {
30
+ if (typeof ctr === 'string') {
31
+ ctr.replace(/<component\s+id="(.+)">([\s\S]*?)<\/component>/gm, (_, tag, template) => {
32
+ setInfo({ tag, template: template.trim() });
33
+ return '';
34
+ });
35
+ } else {
36
+ setInfo(typeof ctr === 'function' ? { Ctor: ctr, tag: ctr.name || fnName(ctr) } : ctr);
37
+ }
38
+ };
39
+
40
+ export const registerTypes = (types?: Array<string | ComponentDefinitionInfo | Constructor>) =>
41
+ types?.forEach(registerType);
42
+
43
+ export const getByTag = (tag: string) => REGISTRY.get(tag) || notFound(tag);
package/core/utils.ts ADDED
@@ -0,0 +1,25 @@
1
+ import { nextId } from 'nextId';
2
+ import { identity } from 'noop';
3
+ import { attributesToString } from 'xml';
4
+
5
+ export const applyValue = (value: any, fn = identity) => (value?.then ? value.then(fn) : fn(value));
6
+
7
+ export const fnName = (ctor: object) => (/^function\s+([\w$]+)\s*\(/.exec(ctor.toString()) || [])[1] || nextId('C');
8
+
9
+ export const methodName = (x: unknown, pre = '') => {
10
+ const s = `${x || ''}`;
11
+ return pre + s[0].toUpperCase() + s.slice(1);
12
+ };
13
+
14
+ export const stringifyState = (attrs?: object | Hash) => {
15
+ if (!attrs) {
16
+ return '';
17
+ }
18
+ const r: string[] = [];
19
+ Object.entries(attrs).forEach(([k, v]) => {
20
+ if (v && k[0] !== '$') {
21
+ r.push(' ' + k + '="' + attributesToString(v) + '"');
22
+ }
23
+ });
24
+ return r.length ? r.join('') : '';
25
+ };
@@ -0,0 +1,25 @@
1
+ import { DomElement, DomTextElement } from './elements';
2
+ import { reflow } from './reflow';
3
+
4
+ export class WebPlatform implements IPlatform {
5
+ rootElement: HTMLElement;
6
+
7
+ constructor(root: HTMLElement | string = 'root') {
8
+ if (typeof root === 'string') {
9
+ root = document.getElementById(root) || window.document.body;
10
+ }
11
+ this.rootElement = root || window.document.body;
12
+ }
13
+
14
+ createTextElement(initials: Hash, _: IComponent): IComponentImplementation {
15
+ return new DomTextElement(initials);
16
+ }
17
+
18
+ createElement(tag: string, initials: Hash, owner: IComponent): IComponentImplementation {
19
+ return new DomElement(tag, initials, owner);
20
+ }
21
+
22
+ reflow(root: IComponent) {
23
+ reflow(root, this.rootElement);
24
+ }
25
+ }
@@ -0,0 +1,190 @@
1
+ import { DomNode, IElement } from './type';
2
+
3
+ const camelize = (key: string, sep = '_', jn = ' ') =>
4
+ ('' + key)
5
+ .split(sep)
6
+ .map((s, i) => (i ? s[0].toUpperCase() + s.slice(1) : s))
7
+ .join(jn);
8
+
9
+ const setListener = ($: IElement, key: string, e: DomNode, v: unknown) => {
10
+ const fnKey = key.split(':')[0];
11
+ if (!v) {
12
+ setAttribute($, key, null);
13
+ } else {
14
+ setAttribute($, key, (ev: Event) => {
15
+ $[fnKey]({ ...e.$dataset }, ev);
16
+ return false;
17
+ });
18
+ }
19
+ };
20
+ export function setAttribute($: IElement, key: string, value: null | string | ((ev: any) => any)) {
21
+ if (value != null) {
22
+ if (typeof value === 'function') {
23
+ if (!$.$listeners) {
24
+ $.$listeners = new Map();
25
+ }
26
+ if (!$.$listeners.has(key)) {
27
+ const [akey, ekey = akey] = key.split(':');
28
+ $.$element.addEventListener(ekey, value, false);
29
+ $.$listeners.set(key, value);
30
+ }
31
+ } else {
32
+ $.$element.setAttribute(key, value);
33
+ }
34
+ } else {
35
+ if ($.$listeners?.has(key)) {
36
+ const [akey, ekey = akey] = key.split(':');
37
+ $.$element.removeEventListener(ekey, $.$listeners.get(key));
38
+ $.$listeners.delete(key);
39
+ } else {
40
+ $.$element.removeAttribute(key);
41
+ }
42
+ }
43
+ }
44
+
45
+ // DOM
46
+ export const ATTR_SETTERS: Hash<($: IElement, e: DomNode, v: unknown) => void> = {
47
+ disabled: (_, e, v) => (e.disabled = v ? true : null),
48
+ class: (_, e, v) => {
49
+ e.className = String(v || '');
50
+ },
51
+ selected: (_, e, v) => (e.selected = v ? true : null),
52
+ value: (_, e, v) => (e.value = v == null ? '' : v),
53
+ checked: (_, e, v) => (e.checked = !!v),
54
+
55
+ init: () => {
56
+ // do nothing
57
+ },
58
+ data: function (_, e, u) {
59
+ const v = u as Hash;
60
+ if (v && e.$dataset) {
61
+ Object.keys(e.$dataset).forEach((k) => {
62
+ if (v[k] == null) {
63
+ e.dataset[camelize(k, '-', '')] = null;
64
+ }
65
+ });
66
+ e.$dataset = { ...e.$dataset, ...v };
67
+ } else {
68
+ e.$dataset = v ? { ...v } : e.$dataset || {};
69
+ }
70
+ if (v) {
71
+ Object.keys(v).forEach((k) => {
72
+ e.dataset[camelize(k, '-', '')] = v[k];
73
+ });
74
+ }
75
+ },
76
+
77
+ click: ($, e, v) => setListener($, 'click:click', e, v),
78
+ ['bubble-click']: ($, e, v) => setListener($, 'bubble-click:click', e, v),
79
+ blur: ($, e, v) => setListener($, 'blur:blur', e, v),
80
+ dblclick: ($, e, v) => setListener($, 'dblclick:dblclick', e, v),
81
+ scroll: ($, e, v) => setListener($, 'scroll:scroll', e, v),
82
+
83
+ touchstart: function ($, e, v) {
84
+ const h = !v
85
+ ? null
86
+ : (ev: TouchEvent & { pageX: number; pageY: number }) => {
87
+ $.touchstart(
88
+ {
89
+ ...e.$dataset,
90
+ x: ev.pageX || ev.changedTouches[0].screenX,
91
+ y: ev.pageY || ev.changedTouches[0].screenY,
92
+ },
93
+ ev
94
+ );
95
+ return false;
96
+ };
97
+ setAttribute($, 'touchstart:touchstart', h);
98
+ setAttribute($, 'touchstart:mousedown', h);
99
+ },
100
+ touch: function ($, e, v) {
101
+ const data = { ...e.$dataset };
102
+ const hs = !v
103
+ ? null
104
+ : (ev: TouchEvent & { pageX: number; pageY: number }) => {
105
+ data.active = true;
106
+ data.x = ev.pageX || ev.changedTouches[0].screenX;
107
+ data.y = ev.pageY || ev.changedTouches[0].screenY;
108
+ return false;
109
+ };
110
+ setAttribute($, 'touch:touchstart', hs);
111
+ setAttribute($, 'touch:mousedown', hs);
112
+ const h = !v
113
+ ? () => null
114
+ : (stop: boolean = false) =>
115
+ (ev: TouchEvent & { pageX: number; pageY: number }) => {
116
+ if (data.active) {
117
+ data.active = !stop;
118
+
119
+ data.xx = ev.pageX || ev.changedTouches[0].screenX;
120
+ data.yy = ev.pageY || ev.changedTouches[0].screenY;
121
+ data.dx = data.xx - data.x;
122
+ data.dy = data.yy - data.y;
123
+ $.touch(data, ev);
124
+ }
125
+
126
+ return false;
127
+ };
128
+ setAttribute($, 'touch:touchcancel', h(true));
129
+ setAttribute($, 'touch:touchend', h(true));
130
+ setAttribute($, 'touch:mouseup', h(true));
131
+ setAttribute($, 'touch:touchmove', h(false));
132
+ setAttribute($, 'touch:mousemove', h(false));
133
+ },
134
+ keypress: function ($, e, v) {
135
+ setAttribute(
136
+ $,
137
+ 'keypress:keyup',
138
+ !v
139
+ ? null
140
+ : (ev: KeyboardEvent) => {
141
+ if (ev.keyCode !== 13 && ev.keyCode !== 27) {
142
+ $.keypress?.({ value: e.value, ...e.$dataset }, ev);
143
+ setTimeout(() => e.focus(), 0);
144
+ }
145
+ return false;
146
+ }
147
+ );
148
+ },
149
+ enter: function ($, e, v) {
150
+ setAttribute(
151
+ $,
152
+ 'enter:keyup',
153
+ !v
154
+ ? null
155
+ : (ev: KeyboardEvent) => {
156
+ if (ev.keyCode === 13) {
157
+ $.enter({ value: e.value, ...e.$dataset }, ev);
158
+ }
159
+ if (ev.keyCode === 13 || ev.keyCode === 27) {
160
+ e.blur();
161
+ }
162
+ return false;
163
+ }
164
+ );
165
+ },
166
+ change: function ($, e, v) {
167
+ setAttribute(
168
+ $,
169
+ 'change:change',
170
+ !v
171
+ ? null
172
+ : (ev: Event) => {
173
+ $.change({ value: e.value, ...e.$dataset }, ev);
174
+ return false;
175
+ }
176
+ );
177
+ },
178
+ toggle: function ($, e, v) {
179
+ setAttribute(
180
+ $,
181
+ 'toggle:change',
182
+ !v
183
+ ? null
184
+ : (ev: Event) => {
185
+ $.toggle({ value: e.checked, ...e.$dataset }, ev);
186
+ return false;
187
+ }
188
+ );
189
+ },
190
+ };
@@ -0,0 +1,63 @@
1
+ import { ATTR_SETTERS, setAttribute } from './attributes';
2
+ import { DomNode, IElement } from './type';
3
+
4
+ export class DomElement implements IComponentImplementation, IElement {
5
+ $: IComponent;
6
+ $element: DomNode;
7
+ $listeners: any;
8
+
9
+ init?: () => any;
10
+ [key: string]: unknown;
11
+
12
+ constructor(tag: string, _: Hash, $: IComponent) {
13
+ this.$ = $;
14
+ this.$element = document.createElement(tag) as DomNode;
15
+ this.$element.impl = this;
16
+ }
17
+
18
+ done() {
19
+ this.$element?.parentElement?.removeChild(this.$element);
20
+ }
21
+
22
+ stateChanged(changes: Map<string, any>) {
23
+ const attrs = this;
24
+ const e = this.$element;
25
+ // this.$.log('changes', changes)
26
+ changes.forEach((value, key) => {
27
+ if (key[0] === '$') return;
28
+ if (value !== attrs[key]) {
29
+ const setter = ATTR_SETTERS[key];
30
+ if (setter) {
31
+ setter(this, e, value);
32
+ } else if (key.slice(0, 5) === 'data-') {
33
+ ATTR_SETTERS.data(this, e, {
34
+ ...e.$dataset,
35
+ [key.slice(5)]: value,
36
+ });
37
+ } else {
38
+ setAttribute(this, key, value);
39
+ }
40
+ }
41
+ attrs[key] = value;
42
+ });
43
+ }
44
+ }
45
+
46
+ export class DomTextElement implements IComponentImplementation {
47
+ $element: Text;
48
+ [key: string]: unknown;
49
+
50
+ constructor(attrs: Hash) {
51
+ this.$element = document.createTextNode(String(attrs.text || ''));
52
+ }
53
+
54
+ done() {
55
+ this.$element.parentElement?.removeChild(this.$element);
56
+ }
57
+
58
+ stateChanged(changes: Map<string, any>) {
59
+ if (changes.has('text')) {
60
+ this.$element.textContent = changes.get('text');
61
+ }
62
+ }
63
+ }
@@ -0,0 +1,16 @@
1
+ import { CArrmaturaNode } from '../core';
2
+ import { WebPlatform } from './WebPlatform';
3
+
4
+ export type ArrmaturaOptionsForWeb = ArrmaturaOptions & {
5
+ rootElement?: HTMLElement;
6
+ };
7
+
8
+ export function arrmatura(
9
+ template: string,
10
+ { rootElement, pipes, functions = pipes, types, resources }: ArrmaturaOptionsForWeb & { pipes: Hash<Pipe> }
11
+ ) {
12
+ const node = new CArrmaturaNode(template, { functions, types, resources }, new WebPlatform(rootElement));
13
+ node.launch();
14
+ }
15
+
16
+ Object.defineProperty(window, 'arrmatura', { value: arrmatura });
@@ -0,0 +1,28 @@
1
+ import { DomNode } from './type';
2
+
3
+ function appendElt(e: DomNode, p: DomNode | HTMLElement, cursor?: DomNode): DomNode {
4
+ const before = cursor ? cursor.nextSibling : p.firstChild;
5
+ if (!before) {
6
+ if (p !== e.parentElement) {
7
+ p.appendChild(e);
8
+ }
9
+ } else if (e !== before) {
10
+ p.insertBefore(e, before);
11
+ }
12
+ return e;
13
+ }
14
+
15
+ export function reflow($: IComponent, parent: DomNode | HTMLElement, cursor?: DomNode) {
16
+ if ($.$children) {
17
+ for (let p of $.$children.values()) {
18
+ const e = p.$impl.$element as DomNode;
19
+ if (e) {
20
+ reflow(p, e);
21
+ cursor = appendElt(e, parent, cursor);
22
+ } else {
23
+ cursor = reflow(p, parent, cursor);
24
+ }
25
+ }
26
+ }
27
+ return cursor;
28
+ }
@@ -0,0 +1,18 @@
1
+ export interface DomNode extends HTMLElement {
2
+ disabled: boolean | null;
3
+ selected: boolean | null;
4
+ value: unknown;
5
+ checked: boolean;
6
+ dataset: any;
7
+ focus(): void;
8
+ blur(): void;
9
+ $dataset: any;
10
+ impl?: IElement;
11
+ }
12
+
13
+ export interface IElement {
14
+ init?: () => any;
15
+ $listeners: any;
16
+ $element: DomNode;
17
+ [key: string]: any;
18
+ }